Added advertiser test.

Change-Id: I8f474e837d27b633bed14d6a421eba65436dd449
diff --git a/Makefile b/Makefile
index fc0ecee..054b34d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,3 @@
-
 include ../shared/mojo.mk
 V23_GO_FILES := $(shell find $(JIRI_ROOT) -name "*.go")
 
@@ -13,6 +12,14 @@
 	--config-alias DISCOVERY_DIR=$(PWD) \
 	--config-alias DISCOVERY_BUILD_DIR=$(DISCOVERY_BUILD_DIR)
 
+define CGO_TEST
+	GOPATH="$(GOPATH)" \
+	CGO_CFLAGS="-I$(MOJO_DIR)/src $(CGO_CFLAGS)" \
+	CGO_CXXFLAGS="-I$(MOJO_DIR)/src $(CGO_CXXFLAGS)" \
+	CGO_LDFLAGS="-L$(dir $(MOJO_SHARED_LIB)) -lsystem_thunk $(CGO_LDFLAGS)" \
+	$(GOROOT)/bin/go test -v $1
+
+endef
 all: $(DISCOVERY_BUILD_DIR)/discovery.mojo
 
 
@@ -30,6 +37,10 @@
 $(DISCOVERY_BUILD_DIR)/scanner.mojo: $(V23_GO_FILES) $(MOJO_SHARED_LIB) go/src/mojom/vanadium/discovery/discovery.mojom.go | mojo-env-check
 	$(call MOGO_BUILD,examples/scanner,$@)
 
+discovery_test: $(V23_GO_FILES) $(MOJO_SHARED_LIB) | mojo-env-check
+	echo $(GOPATH)
+	$(call CGO_TEST,vanadium/discovery/internal)
+
 run-advertiser: $(DISCOVERY_BUILD_DIR)/advertiser.mojo $(DISCOVERY_BUILD_DIR)/discovery.mojo
 	sudo $(MOJO_DIR)/src/mojo/devtools/common/mojo_run --config-file $(PWD)/mojoconfig $(MOJO_SHELL_FLAGS) $(MOJO_ANDROID_FLAGS) https://mojo.v.io/advertiser.mojo \
 	--args-for="https://mojo.v.io/discovery.mojo host1"
diff --git a/go/src/vanadium/discovery/discovery.go b/go/src/vanadium/discovery/discovery.go
index 0c826d7..7c4a2d2 100644
--- a/go/src/vanadium/discovery/discovery.go
+++ b/go/src/vanadium/discovery/discovery.go
@@ -2,12 +2,8 @@
 
 import (
 	"log"
-	"sync"
 
 	"v.io/v23"
-	"v.io/v23/context"
-	"v.io/v23/discovery"
-	"v.io/v23/verror"
 	idiscovery "v.io/x/ref/lib/discovery"
 	"v.io/x/ref/lib/discovery/plugins/ble"
 	"v.io/x/ref/lib/discovery/plugins/mdns"
@@ -15,6 +11,7 @@
 	_ "v.io/x/ref/runtime/factories/generic"
 
 	mojom "mojom/vanadium/discovery"
+	"vanadium/discovery/internal"
 
 	"mojo/public/go/application"
 	"mojo/public/go/bindings"
@@ -24,126 +21,9 @@
 //#include "mojo/public/c/system/types.h"
 import "C"
 
-type discoveryService struct {
-	mu           sync.Mutex
-	ctx          *context.T
-	s            discovery.T
-	trigger      *idiscovery.Trigger
-	nextAdv      int32
-	pendingAdvs  map[int32]chan struct{}
-	nextScan     int32
-	pendingScans map[int32]chan struct{}
-}
-
-func convertToErrorStruct(err error) *mojom.Error {
-	outErr := &mojom.Error{
-		Id:  "v.io/verror/Unknown",
-		Msg: err.Error(),
-	}
-	if e, ok := err.(verror.E); ok {
-		outErr.Id = string(e.ID)
-		outErr.Action = int32(e.Action)
-	}
-	return outErr
-}
-
-func (d *discoveryService) Advertise(s mojom.Service, patterns []string) (int32, *mojom.Error, error) {
-	vService := discovery.Service{
-		InstanceUuid:  s.InstanceUuid,
-		InterfaceName: s.InterfaceName,
-		Attrs:         discovery.Attributes(s.Attrs),
-		Addrs:         s.Addrs,
-	}
-
-	ctx, c := context.WithCancel(d.ctx)
-
-	err := d.s.Advertise(ctx, vService, nil)
-	if err != nil {
-		return 0, convertToErrorStruct(err), nil
-	}
-	ch := make(chan struct{})
-	d.mu.Lock()
-	id := d.nextAdv
-	d.pendingAdvs[id] = ch
-	d.nextAdv++
-	d.mu.Unlock()
-	d.trigger.Add(c, ch)
-	return id, nil, nil
-}
-
-func (d *discoveryService) StopAdvertising(handle int32) error {
-	d.mu.Lock()
-	ch := d.pendingAdvs[handle]
-	d.mu.Unlock()
-	if ch != nil {
-		close(ch)
-	}
-	return nil
-}
-
-func vServiceTomService(s discovery.Service) mojom.Service {
-	return mojom.Service{
-		InstanceUuid:  s.InstanceUuid,
-		InterfaceName: s.InterfaceName,
-		Attrs:         s.Attrs,
-		Addrs:         s.Addrs,
-	}
-}
-
-func (d *discoveryService) Scan(query string, scanHandler mojom.ScanHandler_Pointer) (int32, *mojom.Error, error) {
-	ctx, c := context.WithCancel(d.ctx)
-	proxy := mojom.NewScanHandlerProxy(scanHandler, bindings.GetAsyncWaiter())
-	scanCh, err := d.s.Scan(ctx, query)
-	if err != nil {
-		return 0, convertToErrorStruct(err), nil
-	}
-	ch := make(chan struct{})
-	d.mu.Lock()
-	id := d.nextScan
-	d.pendingScans[id] = ch
-	d.nextScan++
-	d.mu.Unlock()
-
-	d.trigger.Add(c, ch)
-	go func() {
-		for v := range scanCh {
-			switch value := v.(type) {
-			case discovery.UpdateFound:
-				proxy.Found(vServiceTomService(value.Value.Service))
-			case discovery.UpdateLost:
-				proxy.Lost(vServiceTomService(value.Value.Service))
-			default:
-			}
-		}
-	}()
-	return id, nil, nil
-}
-
-func (d *discoveryService) StopScan(handle int32) error {
-	d.mu.Lock()
-	ch := d.pendingScans[handle]
-	d.mu.Unlock()
-	if ch != nil {
-		close(ch)
-	}
-	return nil
-}
-
-func (d *discoveryService) stop() {
-	d.mu.Lock()
-	for _, ch := range d.pendingScans {
-		close(ch)
-	}
-
-	for _, ch := range d.pendingAdvs {
-		close(ch)
-	}
-	d.mu.Unlock()
-}
-
 type discoveryDelegate struct {
 	stubs    []*bindings.Stub
-	impl     *discoveryService
+	impl     *internal.DiscoveryService
 	shutdown v23.Shutdown
 }
 
@@ -161,13 +41,7 @@
 		log.Println("Failed to start bplugin", err)
 	}
 
-	d.impl = &discoveryService{
-		ctx:          ctx,
-		trigger:      idiscovery.NewTrigger(),
-		s:            idiscovery.New([]idiscovery.Plugin{mplugin, bplugin}),
-		pendingAdvs:  map[int32]chan struct{}{},
-		pendingScans: map[int32]chan struct{}{},
-	}
+	d.impl = internal.NewDiscoveryService(ctx, idiscovery.New([]idiscovery.Plugin{mplugin, bplugin}))
 	d.shutdown = shutdown
 }
 
@@ -192,7 +66,7 @@
 }
 
 func (d *discoveryDelegate) Quit() {
-	d.impl.stop()
+	d.impl.Stop()
 	d.shutdown()
 	for _, stub := range d.stubs {
 		stub.Close()
diff --git a/go/src/vanadium/discovery/internal/discovery.go b/go/src/vanadium/discovery/internal/discovery.go
new file mode 100644
index 0000000..9b4cd17
--- /dev/null
+++ b/go/src/vanadium/discovery/internal/discovery.go
@@ -0,0 +1,162 @@
+package internal
+
+import (
+	"sync"
+
+	"v.io/v23/context"
+	"v.io/v23/discovery"
+	"v.io/v23/verror"
+	idiscovery "v.io/x/ref/lib/discovery"
+
+	"mojo/public/go/bindings"
+	mojom "mojom/vanadium/discovery"
+)
+
+// DiscoveryService implements the mojom interface mojom/vanadium/discovery.DiscoveryService.  It
+// is basically a thin wrapper around the Vanadium Discovery API.
+type DiscoveryService struct {
+	ctx          *context.T
+	s            discovery.T
+	trigger      *idiscovery.Trigger
+
+	// mu protects pending* and next*
+	mu           sync.Mutex
+
+	// The id to assign the next advertisement.
+	nextAdv      int32
+	// A map of advertisement ids to the cancellation channel.  When StopAdvertisement is
+	// called, the channel will be closed.
+	pendingAdvs  map[int32]chan struct{}
+	// The id to assign to the next scan.
+	nextScan     int32
+	// A map of scan id to the cancellataion channel.  When StopScan is called, the channel
+	// for that id will be closed.
+	pendingScans map[int32]chan struct{}
+}
+
+func convertToErrorStruct(err error) *mojom.Error {
+	outErr := &mojom.Error{
+		Id:  "v.io/verror/Unknown",
+		Msg: err.Error(),
+	}
+	if e, ok := err.(verror.E); ok {
+		outErr.Id = string(e.ID)
+		outErr.Action = int32(e.Action)
+	}
+	return outErr
+}
+
+// NewDiscoveryService returns a new DiscoveryService bound to the context and the Vanadium
+// Discovery implementation passed in.
+func NewDiscoveryService(ctx *context.T, vDiscovery discovery.T) *DiscoveryService {
+	return &DiscoveryService{
+		ctx:          ctx,
+		s:            vDiscovery,
+		trigger:      idiscovery.NewTrigger(),
+		pendingAdvs:  map[int32]chan struct{}{},
+		pendingScans: map[int32]chan struct{}{},
+	}
+}
+
+// Advertise advertises the mojom service passed only to the giveen blessing patterns. Returns the
+// handle to this Advertise call.
+func (d *DiscoveryService) Advertise(s mojom.Service, patterns []string) (int32, *mojom.Error, error) {
+	vService := discovery.Service{
+		InstanceUuid:  s.InstanceUuid,
+		InterfaceName: s.InterfaceName,
+		Attrs:         discovery.Attributes(s.Attrs),
+		Addrs:         s.Addrs,
+	}
+
+	ctx, c := context.WithCancel(d.ctx)
+
+	err := d.s.Advertise(ctx, vService, nil)
+	if err != nil {
+		return 0, convertToErrorStruct(err), nil
+	}
+	ch := make(chan struct{})
+	d.mu.Lock()
+	id := d.nextAdv
+	d.pendingAdvs[id] = ch
+	d.nextAdv++
+	d.mu.Unlock()
+	d.trigger.Add(c, ch)
+	return id, nil, nil
+}
+
+// StopAdvertising stops advertising for the given advertising id.
+func (d *DiscoveryService) StopAdvertising(handle int32) error {
+	d.mu.Lock()
+	ch := d.pendingAdvs[handle]
+	delete(d.pendingAdvs, handle)
+	d.mu.Unlock()
+	if ch != nil {
+		close(ch)
+	}
+	return nil
+}
+
+func vServiceTomService(s discovery.Service) mojom.Service {
+	return mojom.Service{
+		InstanceUuid:  s.InstanceUuid,
+		InterfaceName: s.InterfaceName,
+		Attrs:         s.Attrs,
+		Addrs:         s.Addrs,
+	}
+}
+
+// Scan scans for all services that match the query string passed in and calls scanHandler with updates.
+// Returns the handle to this Scan.
+func (d *DiscoveryService) Scan(query string, scanHandler mojom.ScanHandler_Pointer) (int32, *mojom.Error, error) {
+	ctx, c := context.WithCancel(d.ctx)
+	proxy := mojom.NewScanHandlerProxy(scanHandler, bindings.GetAsyncWaiter())
+	scanCh, err := d.s.Scan(ctx, query)
+	if err != nil {
+		return 0, convertToErrorStruct(err), nil
+	}
+	ch := make(chan struct{})
+	d.mu.Lock()
+	id := d.nextScan
+	d.pendingScans[id] = ch
+	d.nextScan++
+	d.mu.Unlock()
+
+	d.trigger.Add(c, ch)
+	go func() {
+		for v := range scanCh {
+			switch value := v.(type) {
+			case discovery.UpdateFound:
+				proxy.Found(vServiceTomService(value.Value.Service))
+			case discovery.UpdateLost:
+				proxy.Lost(vServiceTomService(value.Value.Service))
+			default:
+			}
+		}
+	}()
+	return id, nil, nil
+}
+
+// SopScan Stops the scan.
+func (d *DiscoveryService) StopScan(handle int32) error {
+	d.mu.Lock()
+	ch := d.pendingScans[handle]
+	delete(d.pendingScans, handle)
+	d.mu.Unlock()
+	if ch != nil {
+		close(ch)
+	}
+	return nil
+}
+
+// Stop Stops all scans and advertisements.
+func (d *DiscoveryService) Stop() {
+	d.mu.Lock()
+	for _, ch := range d.pendingScans {
+		close(ch)
+	}
+
+	for _, ch := range d.pendingAdvs {
+		close(ch)
+	}
+	d.mu.Unlock()
+}
diff --git a/go/src/vanadium/discovery/internal/discovery_test.go b/go/src/vanadium/discovery/internal/discovery_test.go
new file mode 100644
index 0000000..dea5351
--- /dev/null
+++ b/go/src/vanadium/discovery/internal/discovery_test.go
@@ -0,0 +1,125 @@
+package internal
+import (
+	"reflect"
+	"testing"
+
+	"v.io/v23/discovery"
+	"v.io/v23/security/access"
+	"v.io/v23/context"
+	idiscovery "v.io/x/ref/lib/discovery"
+	vtest "v.io/x/ref/test"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+
+	mojom "mojom/vanadium/discovery"
+)
+
+
+type mockAdv struct {
+	s discovery.Service
+}
+
+type discoveryMock struct {
+	trigger *idiscovery.Trigger
+	id int64
+	services map[int64]discovery.Service
+	// An item will be put in deleteCh when something has been deleted.
+	deleteCh chan struct{}
+}
+
+
+func (d *discoveryMock) Advertise(ctx *context.T,s discovery.Service, perms access.Permissions) error {
+	currId := d.id
+	d.services[currId] = s
+	d.id++
+	c := func() {
+		delete(d.services, currId)
+		d.deleteCh <- struct{}{}
+	}
+	d.trigger.Add(c, ctx.Done())
+	return nil
+}
+
+func (*discoveryMock) Scan(ctx *context.T, query string) (<-chan discovery.Update, error) {
+	return nil, nil
+}
+
+func compare(t *testing.T, vService discovery.Service, mService mojom.Service) {
+	if !reflect.DeepEqual(vService.Addrs, mService.Addrs) {
+		t.Errorf("addrs not the same: %v, %v", vService.Addrs, mService.Addrs)
+	}
+
+	if vService.InterfaceName != mService.InterfaceName {
+		t.Errorf("interface name not the same: %v, %v", vService.InterfaceName, mService.InterfaceName)
+	}
+
+	if !reflect.DeepEqual(map[string]string(vService.Attrs), mService.Attrs) {
+		t.Errorf("attributes not the same: %v, %v", vService.Attrs, mService.Attrs)
+	}
+}
+
+func TestAdvertising(t *testing.T) {
+	ctx, shutdown := vtest.V23Init()
+	defer shutdown()
+	mock := &discoveryMock{
+		trigger: idiscovery.NewTrigger(),
+		services: map[int64]discovery.Service{},
+		deleteCh: make(chan struct{}),
+	}
+	s := NewDiscoveryService(ctx, mock)
+	testService := mojom.Service {
+		InterfaceName: "v.io/v23/discovery.T",
+		Attrs: map[string]string{
+			"key1": "value1",
+			"key2": "value2",
+		},
+		Addrs: []string{ "addr1", "addr2"},
+	}
+	id, e1, e2 := s.Advertise(testService, nil)
+
+	if e1 != nil || e2 != nil {
+		t.Fatalf("Failed to start service: %v, %v", e1, e2)
+	}
+	if len(mock.services) != 1 {
+		t.Errorf("service missing in mock")
+	}
+
+	for _, service := range mock.services {
+		compare(t, service, testService)
+	}
+
+	testService2 := mojom.Service {
+		InterfaceName: "v.io/v23/naming.T",
+		Attrs: map[string]string{
+			"key1": "value1",
+			"key2": "value2",
+		},
+		Addrs: []string{ "addr1", "addr2"},
+
+	}
+
+	_, e1, e2 = s.Advertise(testService2, nil)
+	if e1 != nil || e2 != nil {
+		t.Fatalf("Failed to start service: %v, %v", e1, e2)
+	}
+
+	s.StopAdvertising(id)
+	// Wait for the deletion to finish.
+	<-mock.deleteCh
+	if len(mock.services) != 1 {
+		t.Errorf("service should have been removed")
+	}
+
+	for _, service := range mock.services {
+		compare(t, service, testService2)
+	}
+
+	s.Stop()
+	<-mock.deleteCh
+	if len(mock.services) != 0 {
+		t.Errorf("service should have been removed")
+	}
+}
+
+
+