blob: ec2180d830442eac41c151a0e5024f9ebcd9e520 [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package discovery_test
import (
"fmt"
"reflect"
"testing"
"time"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/discovery"
"v.io/v23/security"
"v.io/v23/security/access"
wire "v.io/v23/services/syncbase"
"v.io/v23/syncbase"
"v.io/v23/verror"
idiscovery "v.io/x/ref/lib/discovery"
fdiscovery "v.io/x/ref/lib/discovery/factory"
"v.io/x/ref/lib/discovery/global"
"v.io/x/ref/lib/discovery/plugins/mock"
udiscovery "v.io/x/ref/lib/discovery/testutil"
_ "v.io/x/ref/runtime/factories/roaming"
syncdis "v.io/x/ref/services/syncbase/discovery"
"v.io/x/ref/services/syncbase/server/interfaces"
tu "v.io/x/ref/services/syncbase/testutil"
"v.io/x/ref/test"
"v.io/x/ref/test/testutil"
)
func TestSyncgroupDiscovery(t *testing.T) {
_, ctx, sName, rootp, cleanup := tu.SetupOrDieCustom("o:app1:client1", "server",
tu.DefaultPerms(access.AllTypicalTags(), "root:o:app1:client1"))
defer cleanup()
d := tu.CreateDatabase(t, ctx, syncbase.NewService(sName), "d")
collection1 := tu.CreateCollection(t, ctx, d, "c1")
collection2 := tu.CreateCollection(t, ctx, d, "c2")
c1Updates, err := scanAs(ctx, rootp, "o:app1:client1")
if err != nil {
panic(err)
}
c2Updates, err := scanAs(ctx, rootp, "o:app1:client2")
if err != nil {
panic(err)
}
sgId := d.Syncgroup(ctx, "sg1").Id()
spec := wire.SyncgroupSpec{
Description: "test syncgroup sg1",
Perms: tu.DefaultPerms(wire.AllSyncgroupTags, "root:server", "root:o:app1:client1"),
Collections: []wire.Id{collection1.Id()},
}
createSyncgroup(t, ctx, d, sgId, spec, verror.ID(""))
// First update is for syncbase, and not a specific syncgroup.
u := <-c1Updates
attrs := u.Advertisement().Attributes
peer := attrs[wire.DiscoveryAttrPeer]
if peer == "" || len(attrs) != 1 {
t.Errorf("Got %v, expected only a peer name.", attrs)
}
// Client2 should see the same.
if err := expect(c2Updates, &discovery.AdId{}, find, discovery.Attributes{wire.DiscoveryAttrPeer: peer}); err != nil {
t.Error(err)
}
sg1Attrs := discovery.Attributes{
wire.DiscoveryAttrDatabaseName: "d",
wire.DiscoveryAttrDatabaseBlessing: "root:o:app1",
wire.DiscoveryAttrSyncgroupName: "sg1",
wire.DiscoveryAttrSyncgroupBlessing: "root:o:app1:client1",
}
sg2Attrs := discovery.Attributes{
wire.DiscoveryAttrDatabaseName: "d",
wire.DiscoveryAttrDatabaseBlessing: "root:o:app1",
wire.DiscoveryAttrSyncgroupName: "sg2",
wire.DiscoveryAttrSyncgroupBlessing: "root:o:app1:client1",
}
// Then we should see an update for the created syncgroup.
var sg1AdId discovery.AdId
if err := expect(c1Updates, &sg1AdId, find, sg1Attrs); err != nil {
t.Error(err)
}
// Now update the spec to add client2 to the permissions.
spec.Perms = tu.DefaultPerms(wire.AllSyncgroupTags, "root:server", "root:o:app1:client1", "root:o:app1:client2")
if err := d.SyncgroupForId(sgId).SetSpec(ctx, spec, ""); err != nil {
t.Fatalf("sg.SetSpec failed: %v", err)
}
// Client1 should see a lost and a found message.
if err := expect(c1Updates, &sg1AdId, both, sg1Attrs); err != nil {
t.Error(err)
}
// Client2 should just now see the found message.
if err := expect(c2Updates, &sg1AdId, find, sg1Attrs); err != nil {
t.Error(err)
}
// Now create a second syncgroup.
sg2Id := d.Syncgroup(ctx, "sg2").Id()
spec2 := wire.SyncgroupSpec{
Description: "test syncgroup sg2",
Perms: tu.DefaultPerms(wire.AllSyncgroupTags, "root:server", "root:o:app1:client1", "root:o:app1:client2"),
Collections: []wire.Id{collection2.Id()},
}
createSyncgroup(t, ctx, d, sg2Id, spec2, verror.ID(""))
// Both clients should see the new syncgroup.
var sg2AdId discovery.AdId
if err := expect(c1Updates, &sg2AdId, find, sg2Attrs); err != nil {
t.Error(err)
}
if err := expect(c2Updates, &sg2AdId, find, sg2Attrs); err != nil {
t.Error(err)
}
spec2.Perms = tu.DefaultPerms(wire.AllSyncgroupTags, "root:server", "root:o:app1:client1")
if err := d.SyncgroupForId(sg2Id).SetSpec(ctx, spec2, ""); err != nil {
t.Fatalf("sg.SetSpec failed: %v", err)
}
if err := expect(c2Updates, &sg2AdId, lose, sg2Attrs); err != nil {
t.Error(err)
}
}
func scanAs(ctx *context.T, rootp security.Principal, as string) (<-chan discovery.Update, error) {
idp := testutil.IDProviderFromPrincipal(rootp)
p := testutil.NewPrincipal()
if err := idp.Bless(p, as); err != nil {
return nil, err
}
ctx, err := v23.WithPrincipal(ctx, p)
if err != nil {
return nil, err
}
dis, err := syncdis.NewDiscovery(ctx, "", 0)
if err != nil {
return nil, err
}
return dis.Scan(ctx, `v.InterfaceName="v.io/x/ref/services/syncbase/server/interfaces/Sync"`)
}
const (
lose = "lose"
find = "find"
both = "both"
)
func expect(ch <-chan discovery.Update, id *discovery.AdId, typ string, want discovery.Attributes) error {
select {
case u := <-ch:
if (u.IsLost() && typ == find) || (!u.IsLost() && typ == lose) {
return fmt.Errorf("IsLost mismatch. Got %v, wanted %v", u, typ)
}
ad := u.Advertisement()
got := ad.Attributes
if id.IsValid() {
if *id != ad.Id {
return fmt.Errorf("mismatched id, got %v, want %v", ad.Id, id)
}
} else {
*id = ad.Id
}
if !reflect.DeepEqual(got, want) {
return fmt.Errorf("got %v, want %v", got, want)
}
if typ == both {
typ = lose
if u.IsLost() {
typ = find
}
return expect(ch, id, typ, want)
}
return nil
case <-time.After(2 * time.Second):
return fmt.Errorf("timed out")
}
}
func TestGlobalSyncbaseDiscovery(t *testing.T) {
ctx, shutdown := test.V23InitWithMounttable()
defer shutdown()
// Create syncbase discovery service with mock neighborhood discovery.
p := mock.New()
df, _ := idiscovery.NewFactory(ctx, p)
defer df.Shutdown()
fdiscovery.InjectFactory(df)
const globalDiscoveryPath = "syncdis"
gdis, err := global.New(ctx, globalDiscoveryPath)
if err != nil {
t.Fatal(err)
}
dis, err := syncdis.NewDiscovery(ctx, globalDiscoveryPath, 100*time.Millisecond)
if err != nil {
t.Fatal(err)
}
// Start the syncbase discovery scan.
scanCh, err := dis.Scan(ctx, ``)
if err != nil {
t.Fatal(err)
}
// The ids of all the ads match.
ad := &discovery.Advertisement{
Id: discovery.AdId{1, 2, 3},
InterfaceName: "v.io/a",
Addresses: []string{"/h1:123/x", "/h2:123/y"},
Attributes: discovery.Attributes{"a": "v"},
}
newad := &discovery.Advertisement{
Id: discovery.AdId{1, 2, 3},
InterfaceName: "v.io/a",
Addresses: []string{"/h1:123/x", "/h3:123/z"},
Attributes: discovery.Attributes{"a": "v"},
}
newadattach := &discovery.Advertisement{
Id: discovery.AdId{1, 2, 3},
InterfaceName: "v.io/a",
Addresses: []string{"/h1:123/x", "/h3:123/z"},
Attributes: discovery.Attributes{"a": "v"},
Attachments: discovery.Attachments{"k": []byte("v")},
}
adinfo := &idiscovery.AdInfo{
Ad: *ad,
Hash: idiscovery.AdHash{1, 2, 3},
TimestampNs: time.Now().UnixNano(),
}
newadattachinfo := &idiscovery.AdInfo{
Ad: *newadattach,
Hash: idiscovery.AdHash{3, 2, 1},
TimestampNs: time.Now().UnixNano(),
}
// Advertise ad via both services, we should get a found update.
adCtx, adCancel := context.WithCancel(ctx)
p.RegisterAd(adinfo)
adStopped, err := gdis.Advertise(adCtx, ad, nil)
if err != nil {
t.Error(err)
}
if err := expect(scanCh, &ad.Id, find, ad.Attributes); err != nil {
t.Error(err)
}
// Lost via both services, we should get a lost update.
p.UnregisterAd(adinfo)
adCancel()
<-adStopped
if err := expect(scanCh, &ad.Id, lose, ad.Attributes); err != nil {
t.Error(err)
}
// Advertise via mock plugin, we should get a found update.
p.RegisterAd(adinfo)
if err := expect(scanCh, &ad.Id, find, ad.Attributes); err != nil {
t.Error(err)
}
// Advertise via global discovery with a newer changed ad, we should get a lost ad
// update and found ad update.
adCtx, adCancel = context.WithCancel(ctx)
adStopped, err = gdis.Advertise(adCtx, newad, nil)
if err != nil {
t.Error(err)
}
if err := expect(scanCh, &newad.Id, both, newad.Attributes); err != nil {
t.Error(err)
}
// If we advertise an ad from neighborhood discovery that is identical to the
// ad found from global discovery, but has attachments, we should prefer it.
p.RegisterAd(newadattachinfo)
if err := expect(scanCh, &newad.Id, both, newad.Attributes); err != nil {
t.Error(err)
}
adCancel()
<-adStopped
}
func TestListenForInvites(t *testing.T) {
_, ctx, sName, rootp, cleanup := tu.SetupOrDieCustom(
"o:app1:client1",
"server",
tu.DefaultPerms(access.AllTypicalTags(), "root:o:app1:client1"))
defer cleanup()
service := syncbase.NewService(sName)
d1 := tu.CreateDatabase(t, ctx, service, "d1")
d2 := tu.CreateDatabase(t, ctx, service, "d2")
collections := []wire.Id{}
for _, d := range []syncbase.Database{d1, d2} {
for _, n := range []string{"c1", "c2"} {
c := tu.CreateCollection(t, ctx, d, d.Id().Name+n)
collections = append(collections, c.Id())
}
}
d1invites, err := listenForInvitesAs(ctx, rootp, "o:app1:client1", d1.Id())
if err != nil {
panic(err)
}
d2invites, err := listenForInvitesAs(ctx, rootp, "o:app1:client1", d2.Id())
if err != nil {
panic(err)
}
if err := advertiseAndFindSyncgroup(t, ctx, d1, collections[0], d1invites); err != nil {
t.Error(err)
}
if err := advertiseAndFindSyncgroup(t, ctx, d2, collections[2], d2invites); err != nil {
t.Error(err)
}
if err := advertiseAndFindSyncgroup(t, ctx, d1, collections[1], d1invites); err != nil {
t.Error(err)
}
if err := advertiseAndFindSyncgroup(t, ctx, d2, collections[3], d2invites); err != nil {
t.Error(err)
}
}
func listenForInvitesAs(ctx *context.T, rootp security.Principal, as string, db wire.Id) (<-chan syncdis.Invite, error) {
ch := make(chan syncdis.Invite)
return ch, syncdis.ListenForInvites(tu.NewCtx(ctx, rootp, as), db, ch)
}
func advertiseAndFindSyncgroup(
t *testing.T,
ctx *context.T,
d syncbase.Database,
collection wire.Id,
invites <-chan syncdis.Invite) error {
sgId := wire.Id{Name: collection.Name + "sg", Blessing: collection.Blessing}
ctx.Infof("creating %v", sgId)
spec := wire.SyncgroupSpec{
Description: "test syncgroup",
Perms: tu.DefaultPerms(wire.AllSyncgroupTags, "root:server", "root:o:app1:client1"),
Collections: []wire.Id{collection},
}
createSyncgroup(t, ctx, d, sgId, spec, verror.ID(""))
// We should see an invite for sg1.
inv := <-invites
if inv.Syncgroup != sgId {
return fmt.Errorf("got %v, want %v", inv.Syncgroup, sgId)
}
return nil
}
func createSyncgroup(t *testing.T, ctx *context.T, d syncbase.Database, sgId wire.Id, spec wire.SyncgroupSpec, errID verror.ID) syncbase.Syncgroup {
sg := d.SyncgroupForId(sgId)
info := wire.SyncgroupMemberInfo{SyncPriority: 8}
if err := sg.Create(ctx, spec, info); verror.ErrorID(err) != errID {
tu.Fatalf(t, "Create SG %+v failed: %v", sgId, err)
}
return sg
}
func raisePeer(t *testing.T, label string, ctx *context.T) <-chan struct{} {
ad := discovery.Advertisement{
InterfaceName: interfaces.SyncDesc.PkgPath + "/" + interfaces.SyncDesc.Name,
Attributes: map[string]string{wire.DiscoveryAttrPeer: label},
}
suffix := ""
eps := udiscovery.ToEndpoints("addr1:123")
mockServer := udiscovery.NewMockServer(eps)
done, err := idiscovery.AdvertiseServer(ctx, nil, mockServer, suffix, &ad, nil)
if err != nil {
t.Fatal(err)
}
return done
}
// Do basic tests to check that listen for peers functions as expected.
// 3 peers are raised in this test and multiple scanners are used.
// Note: All scanners will see the next found peer even if they haven't
// consumed the channel for it yet. The effective buffer size is chan size + 1.
func TestListenForPeers(t *testing.T) {
// Setup a base context that is given a rooted principal.
baseCtx, shutdown := test.V23Init()
defer shutdown()
rootp := testutil.NewPrincipal("root")
ctx := tu.NewCtx(baseCtx, rootp, "o:app")
// Discovery is also mocked out for future "fake" ads.
df, _ := idiscovery.NewFactory(ctx, mock.New())
fdiscovery.InjectFactory(df)
// Raise Peer 1.
ctx1, cancel1 := context.WithCancel(ctx)
defer cancel1()
done1 := raisePeer(t, "peer1", ctx1)
// 1a: Peer 1 can only see themselves.
ctx1a, cancel1a := context.WithCancel(ctx1)
peerChan1a, err := listenForPeersAs(ctx1a, rootp, "client1")
if err != nil {
panic(err)
}
checkExpectedPeers(t, peerChan1a, 1, 0)
cancel1a()
confirmCleanupPeer(t, peerChan1a)
// Raise Peer 2.
ctx2, cancel2 := context.WithCancel(ctx)
defer cancel2()
raisePeer(t, "peer2", ctx2)
// 2a is special and will run until the end of the test. It reads 0 peers now.
ctx2a, cancel2a := context.WithCancel(ctx2)
peerChan2a, err := listenForPeersAs(ctx2a, rootp, "client2")
if err != nil {
panic(err)
}
// 2e is special and will run until the end of the test. It reads 2 peers now.
ctx2e, cancel2e := context.WithCancel(ctx2)
peerChan2e, err := listenForPeersAs(ctx2e, rootp, "client2")
if err != nil {
panic(err)
}
checkExpectedPeers(t, peerChan2e, 2, 0)
// 1b and 2b: Peer 1 and Peer 2 can see each other.
ctx1b, cancel1b := context.WithCancel(ctx1)
peerChan1b, err := listenForPeersAs(ctx1b, rootp, "client1")
if err != nil {
panic(err)
}
checkExpectedPeers(t, peerChan1b, 2, 0)
cancel1b()
confirmCleanupPeer(t, peerChan1b)
ctx2b, cancel2b := context.WithCancel(ctx2)
peerChan2b, err := listenForPeersAs(ctx2b, rootp, "client2")
if err != nil {
panic(err)
}
checkExpectedPeers(t, peerChan2b, 2, 0)
cancel2b()
confirmCleanupPeer(t, peerChan2b)
// Raise Peer 3.
ctx3, cancel3 := context.WithCancel(ctx)
defer cancel3()
done3 := raisePeer(t, "peer3", ctx3)
// 2c is special and will run until the end of the test. It reads 3 peers now.
ctx2c, cancel2c := context.WithCancel(ctx2)
peerChan2c, err := listenForPeersAs(ctx2c, rootp, "client2")
if err != nil {
panic(err)
}
checkExpectedPeers(t, peerChan2c, 3, 0)
// Stop Peer 1 by canceling its context and waiting for the ad to disappear.
cancel1()
<-done1
// The "remove" notification goroutines might race, so wait a little bit.
// It is okay to wait since this is purely for test simplification purposes.
<-time.After(time.Millisecond * 10)
// 2c also sees lost 1.
checkExpectedPeers(t, peerChan2c, 0, 1)
// 2d and 3d: Peer 2 and Peer 3 see each other and nobody else.
ctx2d, cancel2d := context.WithCancel(ctx2)
peerChan2d, err := listenForPeersAs(ctx2d, rootp, "client2")
if err != nil {
panic(err)
}
checkExpectedPeers(t, peerChan2d, 2, 0)
cancel2d()
confirmCleanupPeer(t, peerChan2d)
ctx3d, cancel3d := context.WithCancel(ctx3)
peerChan3d, err := listenForPeersAs(ctx3d, rootp, "client3")
if err != nil {
panic(err)
}
checkExpectedPeers(t, peerChan3d, 2, 0)
cancel3d()
confirmCleanupPeer(t, peerChan3d)
// Stop Peer 3 by canceling its context and waiting for the ad to disappear.
cancel3()
<-done3
// The "remove" notification goroutines might race, so wait a little bit.
// It is okay to wait since this is purely for test simplification purposes.
<-time.After(time.Millisecond * 10)
// 2c also sees lost 3.
checkExpectedPeers(t, peerChan2c, 0, 1)
// We're done with 2c, who saw 3 updates (1, 2, 3) and 2 losses (1 and 3)
cancel2c()
confirmCleanupPeer(t, peerChan2c)
// 2a should see 1, 2, and lose 1. It won't notice #3 since the channel won't buffer it.
checkExpectedPeers(t, peerChan2a, 2, 1)
cancel2a()
confirmCleanupPeer(t, peerChan2a)
// 2e has seen 2 found updates earlier, so peer 3 is buffered in the channel.
// We should see peer 3 and two loss events.
checkExpectedPeers(t, peerChan2e, 1, 2)
cancel2e()
confirmCleanupPeer(t, peerChan2e)
}
func listenForPeersAs(ctx *context.T, rootp security.Principal, as string) (<-chan syncdis.Peer, error) {
ch := make(chan syncdis.Peer)
return ch, syncdis.ListenForPeers(tu.NewCtx(ctx, rootp, as), ch)
}
func checkExpectedPeers(t *testing.T, ch <-chan syncdis.Peer, found int, lost int) {
counter := 0
for i := 0; i < found+lost; i++ {
select {
case peer, ok := <-ch:
if !ok {
t.Error("peer channel shouldn't be closed yet")
} else {
if !peer.Lost {
counter++
}
}
case <-time.After(time.Second * 1):
t.Errorf("timed out without seeing enough peers. Found %d in %d updates, but needed %d in %d", counter, i, found, found+lost)
return
}
}
if counter != found {
t.Errorf("Found %d peers, expected %d", counter, found)
}
}
func confirmCleanupPeer(t *testing.T, ch <-chan syncdis.Peer) {
unwanted, ok := <-ch
if ok {
t.Errorf("found unwanted peer update %v", unwanted)
}
}
// Since the basic found/loss is tested by listenForPeers, this test will focus
// on whether the AdvertiseApp and ListenForPeers functions together.
func TestAdvertiseAndListenForAppPeers(t *testing.T) {
// Setup a base context that is given a rooted principal.
baseCtx, shutdown := test.V23Init()
defer shutdown()
rootp := testutil.NewPrincipal("dev.v.io")
ctx := tu.NewCtx(baseCtx, rootp, "o:app")
// Discovery is also mocked out to mock ads and scans.
df, _ := idiscovery.NewFactory(ctx, mock.New())
fdiscovery.InjectFactory(df)
// First, start Eve, who listens on "todos", "croupier", and syncslides.
ctxMain, cancelMain := context.WithCancel(baseCtx)
todosChan, err := listenForAppPeersAs(ctxMain, rootp, "o:todos:eve")
if err != nil {
panic(err)
}
croupierChan, err := listenForAppPeersAs(ctxMain, rootp, "o:croupier:eve")
if err != nil {
panic(err)
}
syncslidesChan, err := listenForAppPeersAs(ctxMain, rootp, "o:syncslides:eve")
if err != nil {
panic(err)
}
// Create other application peers.
ctxTodosA := tu.NewCtx(baseCtx, rootp, "o:todos:alice")
ctxTodosB := tu.NewCtx(baseCtx, rootp, "o:todos:bob")
ctxTodosC := tu.NewCtx(baseCtx, rootp, "o:todos:carol")
ctxCroupierA := tu.NewCtx(baseCtx, rootp, "o:croupier:alice")
// Advertise "todos" Peer Alice.
ctxTodosAAd, cancelTodosAAd := context.WithCancel(ctxTodosA)
todosDoneAAd, err := syncdis.AdvertiseApp(ctxTodosAAd, nil)
if err != nil {
panic(err)
}
// Advertise "todos" Peer Bob.
ctxTodosBAd, cancelTodosBAd := context.WithCancel(ctxTodosB)
todosDoneBAd, err := syncdis.AdvertiseApp(ctxTodosBAd, nil)
if err != nil {
panic(err)
}
// Start short-term listeners. The one on "todos" sees 2 peers. The one for
// "croupier" sees 0.
ctxT1, cancelT1 := context.WithCancel(ctx)
todosChan1, err := listenForAppPeersAs(ctxT1, rootp, "o:todos:eve")
if err != nil {
panic(err)
}
checkExpectedAppPeers(t, todosChan1, []string{"dev.v.io:o:todos:alice", "dev.v.io:o:todos:bob"}, nil)
cancelT1()
confirmCleanupAppPeer(t, todosChan1)
ctxT2, cancelT2 := context.WithCancel(ctx)
croupierChan1, err := listenForAppPeersAs(ctxT2, rootp, "o:croupier:eve")
if err != nil {
panic(err)
}
checkExpectedAppPeers(t, croupierChan1, nil, nil)
cancelT2()
confirmCleanupAppPeer(t, croupierChan1)
// Let's also consume 2 with Eve's todos scanner.
checkExpectedAppPeers(t, todosChan, []string{"dev.v.io:o:todos:alice", "dev.v.io:o:todos:bob"}, nil)
// Stop advertising todos Peer A.
cancelTodosAAd()
<-todosDoneAAd
// The "remove" notification goroutines might race, so wait a little bit.
// It is okay to wait since this is purely for test simplification purposes.
<-time.After(time.Millisecond * 10)
// So Eve's todos scanner will now see a loss.
checkExpectedAppPeers(t, todosChan, nil, []string{"dev.v.io:o:todos:alice"})
// Start advertising todos Carol and croupier Alice.
ctxTodosCAd, cancelTodosCAd := context.WithCancel(ctxTodosC)
todosDoneCAd, err := syncdis.AdvertiseApp(ctxTodosCAd, nil)
if err != nil {
panic(err)
}
ctxCroupierAAd, cancelCroupierAAd := context.WithCancel(ctxCroupierA)
croupierDoneAAd, err := syncdis.AdvertiseApp(ctxCroupierAAd, nil)
if err != nil {
panic(err)
}
// Expect Eve's todos and croupier to both see 1 new peer each.
checkExpectedAppPeers(t, todosChan, []string{"dev.v.io:o:todos:carol"}, nil)
checkExpectedAppPeers(t, croupierChan, []string{"dev.v.io:o:croupier:alice"}, nil)
// Stop advertising todos Peer B and Peer C and croupier Peer A.
cancelTodosBAd()
cancelTodosCAd()
cancelCroupierAAd()
<-todosDoneBAd
<-todosDoneCAd
<-croupierDoneAAd
// The "remove" notification goroutines might race, so wait a little bit.
// It is okay to wait since this is purely for test simplification purposes.
<-time.After(time.Millisecond * 10)
// Expect that Eve's todos channel will see 2 losses. Croupier's loses 1.
checkExpectedAppPeers(t, todosChan, nil, []string{"dev.v.io:o:todos:bob", "dev.v.io:o:todos:carol"})
checkExpectedAppPeers(t, croupierChan, nil, []string{"dev.v.io:o:croupier:alice"})
// Close the scanners. No additional found/lost events should be detected by Eve.
cancelMain()
confirmCleanupAppPeer(t, todosChan)
confirmCleanupAppPeer(t, croupierChan)
confirmCleanupAppPeer(t, syncslidesChan)
}
func listenForAppPeersAs(ctx *context.T, rootp security.Principal, as string) (<-chan syncdis.AppPeer, error) {
ch := make(chan syncdis.AppPeer)
return ch, syncdis.ListenForAppPeers(tu.NewCtx(ctx, rootp, as), ch)
}
func checkExpectedAppPeers(t *testing.T, ch <-chan syncdis.AppPeer, foundList []string, lostList []string) {
lost := len(lostList)
found := len(foundList)
counter := 0
for i := 0; i < found+lost; i++ {
select {
case peer, ok := <-ch:
if !ok {
t.Error("app peer channel shouldn't be closed yet")
} else {
// Verify that found peers are in the foundList and that lost peers are in the lostList.
if !peer.Lost {
counter++
inFound := false
for _, blessings := range foundList {
if peer.Blessings == blessings {
inFound = true
break
}
}
if !inFound {
t.Errorf("Expected found blessings in %v, but got the app peer: %v", lostList, peer)
}
} else {
inLost := false
for _, blessings := range lostList {
if peer.Blessings == blessings {
inLost = true
break
}
}
if !inLost {
t.Errorf("Expected lost blessings in %v, but got the app peer: %v", lostList, peer)
}
}
}
case <-time.After(time.Second * 1):
t.Errorf("timed out without seeing enough app peers. Found %d in %d updates, but needed %d in %d", counter, i, found, found+lost)
return
}
}
// Having gone through found + lost peers, "counter" must match the number we expected to find.
if counter != found {
t.Errorf("Found %d app peers, expected %d", counter, found)
}
}
func confirmCleanupAppPeer(t *testing.T, ch <-chan syncdis.AppPeer) {
unwanted, ok := <-ch
if ok {
t.Errorf("found unwanted app peer update %v", unwanted)
}
}