测试即使映射已删除键输入也会失败

时间:2018-02-02 06:11:53

标签: unit-testing testing go channel goroutine

我有这个注册表结构,它有一个注销客户端的通道。基本上从地图中删除客户端指针并关闭客户端发送通道。为什么这个测试会一直失败

func (r *SocketRegistry) run() {
    for {
        select {
        case client := <-r.Register:
            r.clients[client.id] = client
        case client := <-r.UnRegister:
            delete(r.clients, client.id)
            close(client.send)
        case payload := <-r.Broadcast:
            var regPayload RegistryPayload
            json.Unmarshal(payload, &regPayload)
            client := r.clients[regPayload.ClientID]
            select {
            case client.send <- payload:
            default:
                close(client.send)
                delete(r.clients, client.id)
            }
        }
    }
}

// GetClient按id

返回客户端指针
func (r *SocketRegistry) GetClient(id string) (*Client, bool) {
    if client, ok := r.clients[id]; ok {
        return client, ok
    }
    return &Client{}, false
}

这是测试

func TestRegisterClient(t *testing.T) {
    registry := Registry()
    go registry.run()
    defer registry.stop()
    var client Client
    client.id = "PROPS"
    client.send = make(chan []byte, 256)

    registry.Register <- &client

    c, _ := registry.GetClient(client.id)
    if client.id != c.id {
        t.Errorf("Expected client with id: %v got: %v", client.id, c.id)
    }

    registry.UnRegister <- &client
    c, ok := registry.GetClient(client.id)
    if ok {
        t.Errorf("Expected false got ok: %v and client id: %v got: %v", ok, client.id, c.id)
    }

}

就像地图永远不会删除密钥一样。如果我添加一些日志语句,那么它会删除密钥,这让我觉得这可能是goroutines的时间问题

1 个答案:

答案 0 :(得分:3)

有一场比赛。在run()被调用之前,无法保证delete(r.clients, client.id)执行registry.GetClient(client.id)

race detector检测并报告问题。

像这样实现GetClient:

// add this field to Registry
get chan getRequest

struct getRequest struct {
     ch chan *Client
     id string
}

func (r *SocketRegistry) GetClient(id string) (*Client, bool) {
    ch := make(chan *Client)
    r.get <- getRequest{id, ch}
    c := <- ch
    if c == nil {
       return &Client{}, false
    }
    return c, true
}

func (r *SocketRegistry) run() {
    for {
        select {
        case gr := <-r.get:
          gr.ch <- r.clients[id]
        case client := <-r.Register:
          ... as before
    }
}

我使用互斥锁代替通道和goroutine来解决这个问题:

func (r *SocketRegistry) register(c *Client) {
    r.mu.Lock()
    defer r.mu.Unlock()
    r.clients[c.id] = c
}

func (r *SocketRegistry) unregister(c *Client) {
    r.mu.Lock()
    defer r.mu.Unlock()
    delete(r.clients, c.id)
    close(c.send)
}

func (r *SocketRegister) get(id string) (*Client, bool) {
    r.mu.Lock()
    defer r.mu.Unlock()
    c, ok := r.clients[id]
    return c, ok
}

func (r *SocketRegistry) send(id string, data []byte) {
   r.mu.Lock()
   defer r.mu.Unlock()
   c := r.clients[id]
   select {
   case c.send <- data:
   default:
      close(c.send)
      delete(r.clients, c.id)
   }
}

Goroutines很棒,但它们并不总是最适合某项工作的工具。