fake.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. /*
  2. Copyright 2017 Google Inc.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. // Package client implements a fake client implementation to be used with
  14. // streaming telemetry collection. It provides a simple Updates queue of data
  15. // to send it should be used to provide an RPC free test infra for user facing
  16. // libraries.
  17. package client
  18. import (
  19. "fmt"
  20. log "github.com/golang/glog"
  21. "context"
  22. "github.com/golang/protobuf/proto"
  23. "notabug.org/themusicgod1/gnmi/client"
  24. )
  25. // New can be replaced for any negative testing you would like to do as well.
  26. //
  27. // New exists for compatibility reasons. Most new clients should use Mock.
  28. // Mock ensures that q.NotificationHandler and ctx aren't forgotten.
  29. var New = func(ctx context.Context, _ client.Destination) (client.Impl, error) {
  30. return &Client{Context: ctx}, nil
  31. }
  32. // Mock overrides a client implementation named typ (most implementation
  33. // libraries have Type constant containing that name) with a fake client
  34. // sending given updates.
  35. //
  36. // See Client documentation about updates slice contents.
  37. func Mock(typ string, updates []interface{}) {
  38. client.RegisterTest(typ, func(ctx context.Context, _ client.Destination) (client.Impl, error) {
  39. c := &Client{
  40. Context: ctx,
  41. Updates: updates,
  42. }
  43. return c, nil
  44. })
  45. }
  46. // Client is the fake of a client implementation. It will provide a simple
  47. // list of updates to send to the generic client.
  48. //
  49. // The Updates slice can contain:
  50. // - client.Notification: passed to query.NotificationHandler
  51. // - proto.Message: passed to query.ProtoHandler
  52. // - error: returned from Recv, interrupts the update stream
  53. // - Block: pauses Recv, proceeds to next update on Unblock
  54. //
  55. // See ExampleClient for sample use case.
  56. type Client struct {
  57. currUpdate int
  58. Updates []interface{}
  59. Handler client.NotificationHandler
  60. ProtoHandler client.ProtoHandler
  61. // BlockAfterSync is deprecated: use Block update as last Updates slice
  62. // element instead.
  63. //
  64. // When BlockAfterSync is set, Client will read from it in Recv after
  65. // sending all Updates before returning ErrStopReading.
  66. // BlockAfterSync is closed when Close is called.
  67. BlockAfterSync chan struct{}
  68. connected bool
  69. Context context.Context
  70. }
  71. // Subscribe implements the client.Impl interface.
  72. func (c *Client) Subscribe(ctx context.Context, q client.Query) error {
  73. c.Handler = q.NotificationHandler
  74. c.ProtoHandler = q.ProtoHandler
  75. return nil
  76. }
  77. // Reset will reset the client to start playing new updates.
  78. func (c *Client) Reset(u []interface{}) {
  79. c.currUpdate = 0
  80. c.Updates = u
  81. }
  82. // Recv will be called for each update the generic client wants to receive.
  83. func (c *Client) Recv() error {
  84. if c.Context == nil {
  85. c.Context = context.Background()
  86. }
  87. if !c.connected && c.Handler != nil {
  88. c.Handler(client.Connected{})
  89. c.connected = true
  90. }
  91. for c.currUpdate < len(c.Updates) {
  92. u := c.Updates[c.currUpdate]
  93. c.currUpdate++
  94. log.V(1).Infof("fake client update: %v", u)
  95. switch v := u.(type) {
  96. case client.Notification:
  97. if c.Handler == nil {
  98. return fmt.Errorf("update %+v is client.Notification but query.NotificationHandler wasn't set", v)
  99. }
  100. return c.Handler(v)
  101. case proto.Message:
  102. if c.ProtoHandler == nil {
  103. return fmt.Errorf("update %+v is proto.Message but query.ProtoHandler wasn't set", v)
  104. }
  105. return c.ProtoHandler(v)
  106. case error:
  107. return v
  108. case Block:
  109. select {
  110. case <-c.Context.Done():
  111. return c.Context.Err()
  112. case <-v:
  113. }
  114. }
  115. }
  116. if c.Handler != nil {
  117. c.Handler(client.Sync{})
  118. }
  119. // We went through all c.Update items.
  120. if c.BlockAfterSync != nil {
  121. log.Info("No more updates, blocking on BlockAfterSync")
  122. select {
  123. case <-c.Context.Done():
  124. return c.Context.Err()
  125. case <-c.BlockAfterSync:
  126. }
  127. }
  128. log.Infof("Recv() returning %v", client.ErrStopReading)
  129. return client.ErrStopReading
  130. }
  131. // Close is a noop in the fake.
  132. func (c *Client) Close() error {
  133. if c.BlockAfterSync != nil {
  134. close(c.BlockAfterSync)
  135. }
  136. return nil
  137. }
  138. // Poll is a noop in the fake.
  139. func (c *Client) Poll() error {
  140. return nil
  141. }
  142. // Set is not supported in fake.
  143. func (c *Client) Set(context.Context, client.SetRequest) (client.SetResponse, error) {
  144. return client.SetResponse{}, client.ErrUnsupported
  145. }
  146. // Block is a special update that lets the stream of updates to be paused.
  147. // See Client docs for usage example.
  148. type Block chan struct{}
  149. // Unblock unpauses the update stream following the Block. Can only be called
  150. // once.
  151. func (b Block) Unblock() { close(b) }