deitch
deitch

Reputation: 14581

How do I tell my test to wait for a callback in a goroutine?

I am using the dockerclient https://github.com/samalba/dockerclient which has a channel-based API to listen for events client.MonitorEvents() and a convenient callback method client.StartMonitorEvents(callbackHandler).

I want to test that the handler gets called. Of course, the dockerclient handles the events in a goroutine.

For now, my handler just spits out a log. If I wait in my test, everything is handled. If I do not, it exits before it handles anything:

func eventCallback(event *dockerclient.Event, ec chan error, args ...interface{}) {
  log.Printf("Received event: %#v\n", *event)
}

My test seems straightforward:

func TestReceiveEvent(t *testing.T) {
   createAndMonitorEvents(server.URL)
   <- eventReady
   eventWriter.Write([]byte(someEvent))
   // test for something here
}

Of course, it doesn't work unless I put in a time.Sleep() because of the goroutine.

How do I tell my test, "wait for the other routine to do its work before running the test", other than putting in some arbitrary sleep? I am looking to test that the event is processed correctly by my handler.

The alternate interface, client.MonitorEvents() returns a channel, which gives me greater control, but the receive off the channel spits out infinite nil events.

UPDATE:

As requested, createAndMonitorEvents is:

func createAndMonitorEvents(url string) {
  // Init the client
  docker, _ := dockerclient.NewDockerClient(url, nil)

  // Listen to events
  stopchan := make(chan struct{})

  go func() {
    eventErrChan, err := docker.MonitorEvents(nil, stopchan)
    if err != nil {
        return
    }

    for e := range eventErrChan {
        if e.Error != nil {
            return
        }
        eventCallback(&e.Event, nil)
    }
    fmt.Println("monitor in place")
  }()
}

Upvotes: 1

Views: 4427

Answers (3)

ZAky
ZAky

Reputation: 1307

I think this can help

A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls Add to set the number of goroutines to wait for. Then each of the goroutines runs and calls Done when finished. At the same time, Wait can be used to block until all goroutines have finished.

http://golang.org/pkg/sync/#example_WaitGroup

Upvotes: 1

twotwotwo
twotwotwo

Reputation: 30057

I think when you get nils with MonitorEvents, you're just seeing that the event channel is closed (the source of MonitorEvents includes a close(eventOrErrorChan), supporting this). evt, ok := <-c lets you directly check if that (ok will be false when it's closed), and for evt := range c will stop after it's closed. In general, receiving from a closed channel is specified to "[yield] the element type's zero value after any previously sent values have been received"

On the question about waiting on a callback: the callback can close a channel. (Or send to it.) Then your test can wait up to a specified length of time with a select:

select {
case <-c:
        /* ...success... */
case <-time.After(5 * time.Second):
        /* timed out */
}

If you know some error conditions cause the handler not to finish, or not to run, it could signal those situations on a different channel, or by sending a different value to c.

Upvotes: 2

evanmcdonnal
evanmcdonnal

Reputation: 48096

"How do I tell my test, "wait for the other routine to do its work before running the test", other than putting in some arbitrary sleep?"

You either send on a channel or close one. Where the caller is receiving execution blocks until a signal occurs. I don't really see how to work your code into something that makes sense though... You can't allocate the stop channel in the function, you have to pass it to the function so the caller can listen on it. Like how does this even compile?

func TestReceiveEvent(t *testing.T) {
   createAndMonitorEvents(server.URL)
   <- eventReady // undeclared variable, and discarding the event you recieved?
   eventWriter.Write([]byte(someEvent)) //and another variable that is not declared
   // test for something here
}

Maybe and idea that will help get you started...

func createAndMonitorEvents(url string, done chan bool) {
      //the codes
      close(done)
}


func TestReceiveEvent(t *testing.T) {
  eventReady := make(chan bool)
    createAndMonitorEvents(server.URL, eventReady)
    <- eventReady
    eventWriter.Write([]byte(someEvent)) // dis still don't exist though
    // test for something here

}

Upvotes: 0

Related Questions