| // 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" |
| "strings" |
| "sync" |
| "time" |
| |
| "mojo/public/go/bindings" |
| "mojo/public/go/system" |
| |
| mojom "mojom/v.io/discovery" |
| |
| _ "v.io/x/ref/runtime/factories/generic" |
| |
| "v.io/mojo/discovery/internal" |
| ) |
| |
| type mockScanHandler struct { |
| ch chan mojom.Update_Pointer |
| } |
| |
| func (h *mockScanHandler) OnUpdate(ptr mojom.Update_Pointer) error { |
| h.ch <- ptr |
| return nil |
| } |
| |
| func scan(d mojom.Discovery, query string) (<-chan mojom.Update_Pointer, func(), error) { |
| ch := make(chan mojom.Update_Pointer, 10) |
| handler := &mockScanHandler{ch} |
| req, ptr := mojom.CreateMessagePipeForScanHandler() |
| stub := mojom.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 |
| } |
| |
| wg := new(sync.WaitGroup) |
| wg.Add(1) |
| go func() { |
| defer wg.Done() |
| for { |
| if err := stub.ServeRequest(); err != nil { |
| connErr, ok := err.(*bindings.ConnectionError) |
| if !ok || !connErr.Closed() { |
| log.Println(err) |
| } |
| break |
| } |
| } |
| }() |
| |
| stop := func() { |
| p := mojom.NewCloserProxy(*closer, bindings.GetAsyncWaiter()) |
| p.Close() |
| p.Close_Proxy() |
| stub.Close() |
| wg.Wait() |
| close(ch) |
| } |
| return ch, stop, nil |
| } |
| |
| func scanAndMatch(d mojom.Discovery, query string, wants ...mojom.Advertisement) error { |
| const timeout = 10 * time.Second |
| |
| var err error |
| for now := time.Now(); time.Since(now) < timeout; { |
| var updatePtrs []mojom.Update_Pointer |
| updatePtrs, err = doScan(d, query, len(wants)) |
| if err != nil { |
| return err |
| } |
| err = matchFound(updatePtrs, wants...) |
| if err == nil { |
| return nil |
| } |
| } |
| return err |
| } |
| |
| func doScan(d mojom.Discovery, query string, expectedUpdates int) ([]mojom.Update_Pointer, error) { |
| scanCh, stop, err := scan(d, query) |
| if err != nil { |
| return nil, err |
| } |
| defer func() { |
| go func() { |
| for range scanCh { |
| } |
| }() |
| stop() |
| }() |
| |
| updatePtrs := make([]mojom.Update_Pointer, 0, expectedUpdates) |
| for { |
| var timer <-chan time.Time |
| if len(updatePtrs) >= expectedUpdates { |
| timer = time.After(5 * time.Millisecond) |
| } |
| |
| select { |
| case ptr := <-scanCh: |
| updatePtrs = append(updatePtrs, ptr) |
| case <-timer: |
| return updatePtrs, nil |
| } |
| } |
| } |
| |
| func matchFound(updatePtrs []mojom.Update_Pointer, wants ...mojom.Advertisement) error { |
| return match(updatePtrs, false, wants...) |
| } |
| |
| func matchLost(updatePtrs []mojom.Update_Pointer, wants ...mojom.Advertisement) error { |
| return match(updatePtrs, true, wants...) |
| } |
| |
| func match(updatePtrs []mojom.Update_Pointer, lost bool, wants ...mojom.Advertisement) error { |
| updateMap := make(map[[internal.AdIdLen]uint8]mojom.Update) |
| updates := make([]mojom.Update, 0) |
| for _, ptr := range updatePtrs { |
| update := mojom.NewUpdateProxy(ptr, bindings.GetAsyncWaiter()) |
| defer update.Close_Proxy() |
| |
| id, _ := update.GetId() |
| updateMap[id] = update |
| updates = append(updates, update) |
| } |
| for _, want := range wants { |
| update := updateMap[*want.Id] |
| if update == nil { |
| break |
| } |
| if got, _ := update.IsLost(); got != lost { |
| break |
| } |
| if !updateEqual(update, want) { |
| break |
| } |
| delete(updateMap, *want.Id) |
| } |
| if len(updateMap) == 0 { |
| return nil |
| } |
| return fmt.Errorf("Match failed; got %v, but wanted %v", updatesToDebugString(updates), adsToDebugString(wants)) |
| } |
| |
| func updateEqual(update mojom.Update, ad mojom.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() |
| h.Wait(system.MOJO_HANDLE_SIGNAL_READABLE, system.MOJO_DEADLINE_INDEFINITE) |
| r, got := h.ReadData(system.MOJO_READ_DATA_FLAG_ALL_OR_NONE) |
| if r != system.MOJO_RESULT_OK { |
| return false |
| } |
| if !bytes.Equal(got, v) { |
| return false |
| } |
| } |
| } |
| return true |
| } |
| |
| func adsToDebugString(ads []mojom.Advertisement) string { |
| var strs []string |
| for _, ad := range ads { |
| strs = append(strs, adToDebugString(ad)) |
| } |
| return "[]" + strings.Join(strs, ", ") + "]" |
| } |
| |
| func adToDebugString(ad mojom.Advertisement) string { |
| return "{" + strings.Join(dumpFields(ad), ", ") + "}" |
| } |
| |
| func updatesToDebugString(updates []mojom.Update) string { |
| var strs []string |
| for _, u := range updates { |
| strs = append(strs, updateToDebugString(u)) |
| } |
| return "[]" + strings.Join(strs, ", ") + "]" |
| } |
| |
| func updateToDebugString(update mojom.Update) string { |
| lost, _ := update.IsLost() |
| ad, _ := update.GetAdvertisement() |
| return fmt.Sprintf("{%v, %v}", lost, strings.Join(dumpFields(ad), ", ")) |
| } |
| |
| func dumpFields(i interface{}) []string { |
| var fields []string |
| for rv, i := reflect.ValueOf(i), 0; i < rv.NumField(); i++ { |
| fields = append(fields, fmt.Sprint(rv.Field(i))) |
| } |
| return fields |
| } |