duan0708676887
2019-01-11 11:15
浏览 61
已采纳

在Go中测试连接的正确方法

I am covering project with tests and for that purpose I need dummy TCP Server, which could accept connection, write/read data to/from it, close it etc... I have found this question on stack overflow, covering mocking connection, but it doesn't cover what I actually need to test.

My idea relies on this article as starting point, but when I started implementing channel to let server write some data to newly opened connection, I got stuck with undebuggable deadlock in writing to channel.

What I want to achieve is to write some data to server's channel, say sendingQueue chan *[]byte, so later corresponding []byte will be sent to newly established connection.

During these little research I have tried debugging and printing out messages before/after sending data to channel and trying to send / read data from channel in different places of program.

What I found out:

  1. My idea works if I add data directly in handleConnection with

    go func() {
       f := []byte("test.")
       t.sendingQueue <- &f
    }()
    
  2. My idea doesn't work if I push data to channel from TestUtils_TestingTCPServer_WritesRequest in any form, either with func (t *TCPServer) Put(data *[]byte) (err error) or directly with:

    go func(queue chan *[]byte, data *[]byte) {
           queue <- data
    }(t.sendingQueue, &payload)
    
  3. It doesn't matter if channel is buffered or not.

So, obviously, there is something wrong either with the way I debug my code (I didn't dive into cli dlv, using just IDE debugger), or something that I completely miss about working with go channels, goroutines or net.Conn module.

For convenience public gist with full code is available. Note — there is // INIT part in the TestUtils_TestingTCPServer_WritesRequest which is required to run/debug single test. It should be commented out when running go test in the directory.

utils.go:


    // NewServer creates a new Server using given protocol
    // and addr.
    func NewTestingTCPServer(protocol, addr string) (*TCPServer, error) {
        switch strings.ToLower(protocol) {
        case "tcp":
            return &TCPServer{
                addr:         addr,
                sendingQueue: make(chan *[]byte, 10),
            }, nil
        case "udp":
        }
        return nil, errors.New("invalid protocol given")
    }

    // TCPServer holds the structure of our TCP
    // implementation.
    type TCPServer struct {
        addr         string
        server       net.Listener
        sendingQueue chan *[]byte
    }

    func (t *TCPServer) Run() (err error) {}
    func (t *TCPServer) Close() (err error) {}
    func (t *TCPServer) Put(data *[]byte) (err error) {}
    func (t *TCPServer) handleConnection(conn net.Conn){
        // <...>

        // Putting data here successfully sends it via freshly established
        // Connection:

        // go func() {
        //  f := []byte("test.")
        //  t.sendingQueue <- &f
        // }()
        for {
            fmt.Printf("Started for loop
")
            select {
            case data := <-readerChannel:
                fmt.Printf("Read written data
")
                writeBuffer.Write(*data)
                writeBuffer.Flush()
            case data := <-t.sendingQueue:
                fmt.Printf("Read pushed data
")
                writeBuffer.Write(*data)
                writeBuffer.Flush()
            case <-ticker:
                fmt.Printf("Tick
")
                return
            }
            fmt.Printf("Finished for loop
")
        }
    } 

utils_test.go


    func TestUtils_TestingTCPServer_WritesRequest(t *testing.T) {

        payload := []byte("hello world
")

        // <...> In gist here is placed INIT piece, which
        // is required to debug single test

        fmt.Printf("Putting payload into queue
")
        // This doesn't affect channel
        err = utilTestingSrv.Put(&payload)
        assert.Nil(t, err)

        // This doesn't work either
        //go func(queue chan *[]byte, data *[]byte) {
        //       queue <- data
        //}(utilTestingSrv.sendingQueue, &payload)

        conn, err := net.Dial("tcp", ":41123")
        if !assert.Nil(t, err) {
            t.Error("could not connect to server: ", err)
        }
        defer conn.Close()

        out := make([]byte, 1024)
        if _, err := conn.Read(out); assert.Nil(t, err) {
            // Need to remove trailing byte 0xa from bytes array to make sure bytes array are equal.
            if out[len(payload)] == 0xa {
                out[len(payload)] = 0x0
            }
            assert.Equal(t, payload, bytes.Trim(out, "\x00"))
        } else {
            t.Error("could not read from connection")
        }
    }

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 邀请回答

1条回答 默认 最新

  • dougu5847 2019-01-11 19:56
    已采纳

    After a help from a colleague and reading the article on how init works, I found a problem.

    It was in init function, which was recreating extra server, due to using := assignment. I also updated code to make sure server runs before net.Dial and conn.Read.

    点赞 打赏 评论