discovery(mojo): add apptest.
MultiPart: 2/2
Change-Id: Ib88656ca6b6e49fe295f4984c8a7d5e0d5965602
diff --git a/Makefile b/Makefile
index 5175e8a..5d924fe 100644
--- a/Makefile
+++ b/Makefile
@@ -33,15 +33,24 @@
packages:
pub upgrade
+# Build mojo app.
.PHONY: build
build: packages gen-mojom $(DISCOVERY_BUILD_DIR)/discovery.mojo
-.PHONY: test
-test: discovery-test
-
.PHONY: gen-mojom
gen-mojom: go/src/mojom/vanadium/discovery/discovery.mojom.go lib/gen/dart-gen/mojom/lib/mojo/discovery.mojom.dart java/generated-src/io/v/mojo/discovery/Advertiser.java
+go/src/mojom/vanadium/discovery/discovery.mojom.go: mojom/vanadium/discovery.mojom | mojo-env-check
+ $(call MOJOM_GEN,$<,.,.,go)
+ gofmt -w $@
+
+lib/gen/dart-gen/mojom/lib/mojo/discovery.mojom.dart: mojom/vanadium/discovery.mojom | mojo-env-check
+ $(call MOJOM_GEN,$<,.,lib/gen,dart)
+ # TODO(nlacasse): mojom_bindings_generator creates bad symlinks on dart
+ # files, so we delete them. Stop doing this once the generator is fixed.
+ # See https://github.com/domokit/mojo/issues/386
+ rm -f lib/gen/mojom/$(notdir $@)
+
# Note: These Java files are checked in.
java/generated-src/io/v/mojo/discovery/Advertiser.java: java/generated-src/mojom/vanadium/discovery.mojom.srcjar
cd java/generated-src/ && jar -xf mojom/vanadium/discovery.mojom.srcjar
@@ -54,11 +63,6 @@
mkdir -p java/generated-src/mojom/vanadium
$(call MOJOM_GEN,$<,.,java/generated-src,java)
-go/src/mojom/vanadium/discovery/discovery.mojom.go: mojom/vanadium/discovery.mojom | mojo-env-check
- $(call MOJOM_GEN,$<,.,.,go)
- gofmt -w $@
-
-
ifdef ANDROID
gradle-build:
cd java && ./gradlew build
@@ -81,28 +85,26 @@
echo "#!mojo mojo:java_handler" > $@
cat build/discovery.zip >> $@
else
-
$(DISCOVERY_BUILD_DIR)/discovery.mojo: $(V23_GO_FILES) $(MOJO_SHARED_LIB) | mojo-env-check
$(call MOGO_BUILD,vanadium/discovery,$@)
endif
+# Tests
+.PHONY: test
+test: unittest apptest
-lib/gen/dart-gen/mojom/lib/mojo/discovery.mojom.dart: mojom/vanadium/discovery.mojom | mojo-env-check
- $(call MOJOM_GEN,$<,.,lib/gen,dart)
- # TODO(nlacasse): mojom_bindings_generator creates bad symlinks on dart
- # files, so we delete them. Stop doing this once the generator is fixed.
- # See https://github.com/domokit/mojo/issues/386
- rm -f lib/gen/mojom/$(notdir $@)
-
-discovery-test: $(V23_GO_FILES) go/src/mojom/vanadium/discovery/discovery.mojom.go | mojo-env-check
+.PHONY: unittest
+unittest: $(V23_GO_FILES) go/src/mojom/vanadium/discovery/discovery.mojom.go | mojo-env-check
$(call MOGO_TEST,-v vanadium/discovery/internal/...)
-clean:
- rm -rf gen
- rm -rf lib/gen/dart-pkg
- rm -rf lib/gen/mojom
- rm -rf $(PACKAGE_MOJO_BIN_DIR)
+.PHONY: apptest
+apptest: mojoapptests $(DISCOVERY_BUILD_DIR)/discovery_apptests.mojo | mojo-env-check
+ $(call MOJO_APPTEST,"mojoapptests")
+$(DISCOVERY_BUILD_DIR)/discovery_apptests.mojo: $(V23_GO_FILES) | mojo-env-check
+ $(call MOGO_BUILD,vanadium/discovery/internal/apptest/main,$@)
+
+# Publish
.PHONY: publish
# NOTE(aghassemi): This must be inside lib in order to be accessible.
PACKAGE_MOJO_BIN_DIR := lib/mojo_services
@@ -129,7 +131,14 @@
mkdir -p $(PACKAGE_MOJO_BIN_DIR)
cp -r gen/mojo/* $(PACKAGE_MOJO_BIN_DIR)
-# Examples.
+# Cleanup
+clean:
+ rm -rf gen
+ rm -rf lib/gen/dart-pkg
+ rm -rf lib/gen/mojom
+ rm -rf $(PACKAGE_MOJO_BIN_DIR)
+
+# Examples
run-advertiser: $(DISCOVERY_BUILD_DIR)/advertiser.mojo $(DISCOVERY_BUILD_DIR)/discovery.mojo
$(call MOJO_RUN,"https://mojo.v.io/advertiser.mojo")
diff --git a/go/src/vanadium/discovery/discovery.go b/go/src/vanadium/discovery/discovery.go
index 229dd68..e81e0b1 100644
--- a/go/src/vanadium/discovery/discovery.go
+++ b/go/src/vanadium/discovery/discovery.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+// +build mojo
+
package main
import (
diff --git a/go/src/vanadium/discovery/internal/apptest/apptest.go b/go/src/vanadium/discovery/internal/apptest/apptest.go
new file mode 100644
index 0000000..6670a7d
--- /dev/null
+++ b/go/src/vanadium/discovery/internal/apptest/apptest.go
@@ -0,0 +1,34 @@
+// 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.
+
+// +build mojo
+
+package apptest
+
+import (
+ "reflect"
+ "regexp"
+ "runtime"
+ "strings"
+ "testing"
+
+ "mojo/public/go/application"
+)
+
+func RunAppTests(mctx application.Context) int {
+ apptests := []func(*testing.T, application.Context){
+ AppTestBasic,
+ }
+
+ var tests []testing.InternalTest
+ for _, apptest := range apptests {
+ qname := runtime.FuncForPC(reflect.ValueOf(apptest).Pointer()).Name()
+ name := qname[strings.LastIndex(qname, ".")+1:]
+ tests = append(tests, testing.InternalTest{name, func(t *testing.T) { apptest(t, mctx) }})
+ }
+
+ // MainStart is not supposed to be called directly, but there is no other way
+ // to run tests programatically at run time.
+ return testing.MainStart(regexp.MatchString, tests, nil, nil).Run()
+}
diff --git a/go/src/vanadium/discovery/internal/apptest/discovery_apptest.go b/go/src/vanadium/discovery/internal/apptest/discovery_apptest.go
new file mode 100644
index 0000000..3c88b23
--- /dev/null
+++ b/go/src/vanadium/discovery/internal/apptest/discovery_apptest.go
@@ -0,0 +1,286 @@
+// 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.
+
+// +build mojo
+
+package apptest
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "log"
+ "reflect"
+ "testing"
+ "time"
+
+ "mojo/public/go/application"
+ "mojo/public/go/bindings"
+ "mojo/public/go/system"
+
+ "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"
+
+ "vanadium/discovery/internal"
+)
+
+func AppTestBasic(t *testing.T, mctx application.Context) {
+ ctx, shutdown := test.V23Init(mctx)
+ defer shutdown()
+
+ df, _ := idiscovery.NewFactory(ctx, mock.New())
+ dfactory.InjectFactory(df)
+
+ ads := []discovery.Advertisement{
+ {
+ Id: &[internal.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 := internal.NewDiscovery(ctx)
+ defer d1.Close()
+
+ var stops []func()
+ for i, ad := range ads {
+ id, closer, e1, e2 := d1.Advertise(ad, nil)
+ if e1 != nil || e2 != nil {
+ t.Fatalf("ad[%d]: failed to advertise: %v, %v", i, e1, e2)
+ }
+ if id == nil {
+ t.Errorf("ad[%d]: got nil id", i)
+ continue
+ }
+ if ad.Id == nil {
+ ads[i].Id = id
+ } else if *id != *ad.Id {
+ t.Errorf("ad[%d]: got ad id %v, but wanted %v", i, *id, *ad.Id)
+ }
+
+ stop := func() {
+ p := discovery.NewCloserProxy(*closer, bindings.GetAsyncWaiter())
+ p.Close()
+ p.Close_Proxy()
+ }
+ stops = append(stops, stop)
+ }
+
+ // 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 := internal.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, stop, err := scan(d2, `v.InterfaceName="v.io/v23/a"`)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer stop()
+ update := <-scanCh
+ if !matchFound([]*discovery.Update_Proxy{update}, ads[0]) {
+ t.Errorf("unexpected scan: %v", update)
+ }
+
+ // Make sure scan returns the lost advertisement when advertising is stopped.
+ stops[0]()
+
+ update = <-scanCh
+ if !matchLost([]*discovery.Update_Proxy{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.
+ stops[1]()
+ if err := scanAndMatch(d2, ""); err != nil {
+ t.Error(err)
+ }
+}
+
+type mockScanHandler struct {
+ ch chan *discovery.Update_Proxy
+}
+
+func (h *mockScanHandler) OnUpdate(ptr discovery.Update_Pointer) error {
+ h.ch <- discovery.NewUpdateProxy(ptr, bindings.GetAsyncWaiter())
+ return nil
+}
+
+func scan(d discovery.Discovery, query string) (<-chan *discovery.Update_Proxy, func(), error) {
+ ch := make(chan *discovery.Update_Proxy)
+ handler := &mockScanHandler{ch}
+ req, ptr := discovery.CreateMessagePipeForScanHandler()
+ stub := discovery.NewScanHandlerStub(req, handler, bindings.GetAsyncWaiter())
+
+ closer, e1, e2 := d.Scan(query, ptr)
+ if e1 != nil {
+ close(ch)
+ return nil, nil, errors.New(e1.Msg)
+ }
+ if e2 != nil {
+ close(ch)
+ return nil, nil, e2
+ }
+
+ go func() {
+ for {
+ if err := stub.ServeRequest(); err != nil {
+ connErr, ok := err.(*bindings.ConnectionError)
+ if !ok || !connErr.Closed() {
+ log.Println(err)
+ }
+ break
+ }
+ }
+ }()
+
+ stop := func() {
+ p := discovery.NewCloserProxy(*closer, bindings.GetAsyncWaiter())
+ p.Close()
+ p.Close_Proxy()
+ close(ch)
+ }
+ return ch, stop, nil
+}
+
+func scanAndMatch(d discovery.Discovery, query string, wants ...discovery.Advertisement) error {
+ const timeout = 3 * time.Second
+
+ var updates []*discovery.Update_Proxy
+ 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 discovery.Discovery, query string, expectedUpdates int) ([]*discovery.Update_Proxy, error) {
+ scanCh, stop, err := scan(d, query)
+ if err != nil {
+ return nil, err
+ }
+ defer stop()
+
+ updates := make([]*discovery.Update_Proxy, 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 = 1 * time.Second
+ }
+
+ select {
+ case update := <-scanCh:
+ updates = append(updates, update)
+ case <-time.After(timeout):
+ return updates, nil
+ }
+ }
+}
+
+func matchFound(updates []*discovery.Update_Proxy, wants ...discovery.Advertisement) bool {
+ return match(updates, false, wants...)
+}
+
+func matchLost(updates []*discovery.Update_Proxy, wants ...discovery.Advertisement) bool {
+ return match(updates, true, wants...)
+}
+
+func match(updates []*discovery.Update_Proxy, lost bool, wants ...discovery.Advertisement) bool {
+ updateMap := make(map[[internal.AdIdLen]uint8]discovery.Update)
+ for _, update := range updates {
+ defer update.Close_Proxy()
+ id, _ := update.GetId()
+ updateMap[id] = update
+ }
+
+ for _, want := range wants {
+ update := updateMap[*want.Id]
+ if update == nil {
+ return false
+ }
+ if !updateEqual(update, want) {
+ return false
+ }
+ delete(updateMap, *want.Id)
+ }
+ return len(updateMap) == 0
+}
+
+func updateEqual(update discovery.Update, ad discovery.Advertisement) bool {
+ if got, _ := update.GetId(); got != *ad.Id {
+ return false
+ }
+ if got, _ := update.GetInterfaceName(); got != ad.InterfaceName {
+ return false
+ }
+ if got, _ := update.GetAddresses(); !reflect.DeepEqual(got, ad.Addresses) {
+ return false
+ }
+ if ad.Attributes != nil {
+ for k, v := range *ad.Attributes {
+ if got, _ := update.GetAttribute(k); got != v {
+ return false
+ }
+ }
+ }
+ if ad.Attachments != nil {
+ for k, v := range *ad.Attachments {
+ h, err := update.GetAttachment(k)
+ if err != nil {
+ return false
+ }
+ defer h.Close()
+ r, got := h.ReadData(system.MOJO_READ_DATA_FLAG_NONE)
+ if r != system.MOJO_RESULT_OK {
+ return false
+ }
+ if !bytes.Equal(got, v) {
+ return false
+ }
+ }
+ }
+ if got, _ := update.GetAdvertisement(); !reflect.DeepEqual(got, ad) {
+ return false
+ }
+ return true
+}
diff --git a/go/src/vanadium/discovery/internal/apptest/main/main.go b/go/src/vanadium/discovery/internal/apptest/main/main.go
new file mode 100644
index 0000000..005c1bc
--- /dev/null
+++ b/go/src/vanadium/discovery/internal/apptest/main/main.go
@@ -0,0 +1,57 @@
+// 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.
+
+// +build mojo
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+
+ "mojo/public/go/application"
+ "mojo/public/go/system"
+
+ "vanadium/discovery/internal/apptest"
+)
+
+//#include "mojo/public/c/system/types.h"
+import "C"
+
+func init() {
+ // Add flag placeholders to suppress warnings on unhandled mojo flags.
+ flag.String("child-connection-id", "", "")
+ flag.String("platform-channel-handle-info", "", "")
+}
+
+type delegate struct{}
+
+func (*delegate) Initialize(mctx application.Context) {
+ os.Args = mctx.Args()
+ if len(os.Args) == 0 {
+ // TODO(jhahn): mojo_run doesn't pass the service url when there is
+ // no flags. See https://github.com/domokit/mojo/issues/586.
+ os.Args = []string{mctx.URL()}
+ }
+ // mojo_test checks the output for the test results since mojo_shell
+ // always exits with 0.
+ if apptest.RunAppTests(mctx) == 0 {
+ fmt.Println("[ PASSED ]")
+ } else {
+ fmt.Println("[ FAILED ]")
+ }
+ mctx.Close()
+}
+
+func (*delegate) AcceptConnection(connection *application.Connection) { connection.Close() }
+func (*delegate) Quit() {}
+
+//export MojoMain
+func MojoMain(handle C.MojoHandle) C.MojoResult {
+ application.Run(&delegate{}, system.MojoHandle(handle))
+ return C.MOJO_RESULT_OK
+}
+
+func main() {}
diff --git a/go/src/vanadium/discovery/internal/discovery.go b/go/src/vanadium/discovery/internal/discovery.go
index a068b3b..7ef3cac 100644
--- a/go/src/vanadium/discovery/internal/discovery.go
+++ b/go/src/vanadium/discovery/internal/discovery.go
@@ -16,7 +16,8 @@
"v.io/v23/discovery"
)
-// TODO(jhahn): Mojom 'const' is ignored in mojom.go. Remove this once it is fixed.
+// TODO(jhahn): Mojom 'const' is ignored in mojom.go.
+// See https://github.com/domokit/mojo/issues/685.
const AdIdLen = 16
// closer implements the mojom.Closer.
@@ -46,93 +47,60 @@
}
func (d *mdiscovery) Advertise(ad mojom.Advertisement, visibility *[]string) (*[AdIdLen]uint8, *mojom.Closer_Pointer, *mojom.Error, error) {
- // There is no way to mock _Pointer or _Request types. So we put Advertise()
- // logic into a separate function doAdvertise() for unit testing.
- closer, err := d.doAdvertise(&ad, visibility)
- if err != nil {
- return nil, nil, v2mError(err), nil
- }
-
- req, ptr := mojom.CreateMessagePipeForCloser()
- stub := mojom.NewCloserStub(req, closer, bindings.GetAsyncWaiter())
- d.serveStub(stub, closer.cancel)
- return ad.Id, &ptr, nil, nil
-}
-
-func (d *mdiscovery) doAdvertise(ad *mojom.Advertisement, visibility *[]string) (*closer, error) {
- vAd := m2vAd(ad)
- vVisibility := m2vVisibility(visibility)
-
ctx, cancel := context.WithCancel(d.ctx)
+
+ vAd := m2vAd(&ad)
+ vVisibility := m2vVisibility(visibility)
done, err := d.d.Advertise(ctx, &vAd, vVisibility)
if err != nil {
cancel()
- return nil, err
+ return nil, nil, v2mError(err), nil
}
- if ad.Id == nil {
- ad.Id = new([AdIdLen]uint8)
- }
- *ad.Id = vAd.Id
+
stop := func() {
cancel()
<-done
}
- return &closer{stop}, nil
-}
+ req, ptr := mojom.CreateMessagePipeForCloser()
+ stub := mojom.NewCloserStub(req, &closer{stop}, bindings.GetAsyncWaiter())
+ d.serveStub(stub, stop)
-type scanHandlerProxy interface {
- passUpdate(update mojom.Update) error
- Close_Proxy()
-}
-
-type scanHandlerProxyImpl struct {
- *mojom.ScanHandler_Proxy
-
- d *mdiscovery
-}
-
-func (p *scanHandlerProxyImpl) passUpdate(update mojom.Update) error {
- req, ptr := mojom.CreateMessagePipeForUpdate()
- stub := mojom.NewUpdateStub(req, update, bindings.GetAsyncWaiter())
- p.d.serveStub(stub, nil)
- return p.OnUpdate(ptr)
+ var id [AdIdLen]uint8
+ id = vAd.Id
+ return &id, &ptr, nil, nil
}
func (d *mdiscovery) Scan(query string, handlerPtr mojom.ScanHandler_Pointer) (*mojom.Closer_Pointer, *mojom.Error, error) {
- // There is no way to mock _Pointer or _Request types. So we put Scan()
- // logic into a separate function doScan() for unit testing.
- proxy := mojom.NewScanHandlerProxy(handlerPtr, bindings.GetAsyncWaiter())
- closer, err := d.doScan(query, &scanHandlerProxyImpl{proxy, d})
- if err != nil {
- return nil, v2mError(err), nil
- }
-
- req, ptr := mojom.CreateMessagePipeForCloser()
- stub := mojom.NewCloserStub(req, closer, bindings.GetAsyncWaiter())
- d.serveStub(stub, closer.cancel)
- return &ptr, nil, nil
-}
-
-func (d *mdiscovery) doScan(query string, proxy scanHandlerProxy) (*closer, error) {
ctx, cancel := context.WithCancel(d.ctx)
+
scanCh, err := d.d.Scan(ctx, query)
if err != nil {
cancel()
- proxy.Close_Proxy()
- return nil, err
+ return nil, v2mError(err), nil
}
+ handler := mojom.NewScanHandlerProxy(handlerPtr, bindings.GetAsyncWaiter())
go func() {
- defer proxy.Close_Proxy()
+ defer handler.Close_Proxy()
for update := range scanCh {
- mUpdate := newMojoUpdate(ctx, update)
- if err := proxy.passUpdate(mUpdate); err != nil {
+ mUpdate := newMojoUpdate(d.ctx, update)
+
+ req, ptr := mojom.CreateMessagePipeForUpdate()
+ stub := mojom.NewUpdateStub(req, mUpdate, bindings.GetAsyncWaiter())
+ if err := handler.OnUpdate(ptr); err != nil {
+ stub.Close()
+ cancel()
return
}
+ d.serveStub(stub, nil)
}
}()
- return &closer{cancel}, nil
+
+ req, ptr := mojom.CreateMessagePipeForCloser()
+ stub := mojom.NewCloserStub(req, &closer{cancel}, bindings.GetAsyncWaiter())
+ d.serveStub(stub, cancel)
+ return &ptr, nil, nil
}
func (d *mdiscovery) serveStub(stub *bindings.Stub, cleanup func()) {
diff --git a/go/src/vanadium/discovery/internal/discovery_test.go b/go/src/vanadium/discovery/internal/discovery_test.go
deleted file mode 100644
index 93e809b..0000000
--- a/go/src/vanadium/discovery/internal/discovery_test.go
+++ /dev/null
@@ -1,199 +0,0 @@
-// 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
-}
diff --git a/go/src/vanadium/discovery/internal/update_test.go b/go/src/vanadium/discovery/internal/update_test.go
index 05a2d8f..0f961a8 100644
--- a/go/src/vanadium/discovery/internal/update_test.go
+++ b/go/src/vanadium/discovery/internal/update_test.go
@@ -58,6 +58,9 @@
}
}
+ // Note that we cannot test attachments in this unit test since it is
+ // using mojo data handle. This test is covered by apptest.
+
mAd := v2mAd(&ad)
if got, _ := mUpdate.GetAdvertisement(); !reflect.DeepEqual(got, mAd) {
t.Errorf("Advertisement: got %v, but want %v", got, mAd)
diff --git a/mojoapptests b/mojoapptests
new file mode 100644
index 0000000..fe6124e
--- /dev/null
+++ b/mojoapptests
@@ -0,0 +1,10 @@
+# This file contains a list of Mojo apptests.
+# For description of the file format, see 'mojo_test'.
+
+tests = [
+ {
+ "test": "https://mojo.v.io/discovery_apptests.mojo",
+ "test-args": [],
+ "shell-args": [],
+ },
+]