blob: 93e809bc39bcc421a5c120bdabd17b8734f22544 [file] [log] [blame]
// Copyright 2015 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 internal
import (
"fmt"
"reflect"
"testing"
"time"
mojom "mojom/vanadium/discovery"
idiscovery "v.io/x/ref/lib/discovery"
dfactory "v.io/x/ref/lib/discovery/factory"
"v.io/x/ref/lib/discovery/plugins/mock"
_ "v.io/x/ref/runtime/factories/generic"
"v.io/x/ref/test"
)
func TestBasic(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
df, _ := idiscovery.NewFactory(ctx, mock.New())
dfactory.InjectFactory(df)
ads := []mojom.Advertisement{
{
Id: &[AdIdLen]uint8{1, 2, 3},
InterfaceName: "v.io/v23/a",
Addresses: []string{"/h1:123/x"},
Attributes: &map[string]string{"a1": "v1"},
},
{
InterfaceName: "v.io/v23/b",
Addresses: []string{"/h1:123/y"},
Attributes: &map[string]string{"b1": "v1"},
},
}
d1 := NewDiscovery(ctx)
defer d1.Close()
var adClosers []mojom.Closer
for i, _ := range ads {
closer, err := d1.(*mdiscovery).doAdvertise(&ads[i], nil)
if err != nil {
t.Fatalf("ad[%d]: failed to advertise: %v", i, err)
}
if ads[i].Id == nil {
t.Errorf("ad[%d]: got nil id", i)
}
adClosers = append(adClosers, closer)
}
// Make sure none of advertisements are discoverable by the same discovery instance.
if err := scanAndMatch(d1, ""); err != nil {
t.Error(err)
}
// Create a new discovery instance. All advertisements should be discovered with that.
d2 := NewDiscovery(ctx)
defer d2.Close()
if err := scanAndMatch(d2, `v.InterfaceName="v.io/v23/a"`, ads[0]); err != nil {
t.Error(err)
}
if err := scanAndMatch(d2, `v.InterfaceName="v.io/v23/b"`, ads[1]); err != nil {
t.Error(err)
}
if err := scanAndMatch(d2, ``, ads...); err != nil {
t.Error(err)
}
// Open a new scan channel and consume expected advertisements first.
scanCh, scanCloser, err := scan(d2, `v.InterfaceName="v.io/v23/a"`)
if err != nil {
t.Fatal(err)
}
defer scanCloser.Close()
update := <-scanCh
if !matchFound([]mojom.Update{update}, ads[0]) {
t.Errorf("unexpected scan: %v", update)
}
// Make sure scan returns the lost advertisement when advertising is stopped.
adClosers[0].Close()
update = <-scanCh
if !matchLost([]mojom.Update{update}, ads[0]) {
t.Errorf("unexpected scan: %v", update)
}
// Also it shouldn't affect the other.
if err := scanAndMatch(d2, `v.InterfaceName="v.io/v23/b"`, ads[1]); err != nil {
t.Error(err)
}
// Stop advertising the remaining one; Shouldn't discover any advertisements.
adClosers[1].Close()
if err := scanAndMatch(d2, ""); err != nil {
t.Error(err)
}
}
type mockScanHandler struct {
ch chan mojom.Update
}
func (m *mockScanHandler) Close_Proxy() { close(m.ch) }
func (m *mockScanHandler) passUpdate(u mojom.Update) error {
m.ch <- u
return nil
}
func scan(d mojom.Discovery, query string) (<-chan mojom.Update, mojom.Closer, error) {
ch := make(chan mojom.Update)
closer, err := d.(*mdiscovery).doScan(query, &mockScanHandler{ch})
if err != nil {
return nil, nil, err
}
return ch, closer, nil
}
func scanAndMatch(d mojom.Discovery, query string, wants ...mojom.Advertisement) error {
const timeout = 10 * time.Second
var updates []mojom.Update
for now := time.Now(); time.Since(now) < timeout; {
var err error
updates, err = doScan(d, query, len(wants))
if err != nil {
return err
}
if matchFound(updates, wants...) {
return nil
}
}
return fmt.Errorf("Match failed; got %v, but wanted %v", updates, wants)
}
func doScan(d mojom.Discovery, query string, expectedUpdates int) ([]mojom.Update, error) {
scanCh, closer, err := scan(d, query)
if err != nil {
return nil, err
}
defer closer.Close()
updates := make([]mojom.Update, 0, expectedUpdates)
for {
timeout := 5 * time.Millisecond
if len(updates) < expectedUpdates {
// Increase the timeout if we do not receive enough updates
// to avoid flakiness in unit tests.
timeout = 5 * time.Second
}
select {
case update := <-scanCh:
updates = append(updates, update)
case <-time.After(timeout):
return updates, nil
}
}
}
func matchFound(updates []mojom.Update, wants ...mojom.Advertisement) bool {
return match(updates, false, wants...)
}
func matchLost(updates []mojom.Update, wants ...mojom.Advertisement) bool {
return match(updates, true, wants...)
}
func match(updates []mojom.Update, lost bool, wants ...mojom.Advertisement) bool {
updateMap := make(map[[AdIdLen]uint8]mojom.Update)
for _, update := range updates {
id, _ := update.GetId()
updateMap[id] = update
}
for _, want := range wants {
update := updateMap[*want.Id]
if update == nil {
return false
}
if got, _ := update.IsLost(); got != lost {
return false
}
if got, _ := update.GetAdvertisement(); !reflect.DeepEqual(got, want) {
return false
}
delete(updateMap, *want.Id)
}
return len(updateMap) == 0
}