从测试中,恐慌:sql:从未返回的连接返回

I get the following panic at run time of my test:

panic: sql: connection returned that was never out

The test

The actual test is part of a test suite, and is defined thus:

func (suite *RowsSuite) TestReadFrom_SanityTest() {
    t := suite.T()

    rows := new(sql.Rows)
    *rows = suite.nonEmptyRows

    assert.True(t, rows.Next())
    // hit the function like it's supposed to be hit
    err := ReadFrom(rows,
        suite.fnStubFalse,
        suite.spots[0],
        suite.spots[1],
        suite.spots[2])

    // There better be no error
    if !assert.Nil(t, err) {
        t.Error(err.ToString(false))
    }

}

The code under test

I'm responsible for testing this function:

// ReadFrom reads values from `rows` into `valuePlaceholders`
func ReadFrom(rows *sql.Rows,
    readerFunc func(rowIndex int, readValues ...interface{}) (bool, *errors.ErrorSt),
    valuePlaceholders ...interface{}) (err *errors.ErrorSt) {

    rowLine := 1
    for rows.Next() {
        if err := rows.Scan(valuePlaceholders...); err != nil {
            return errors.Database().AddDetails(err.Error(), valuePlaceholders)
        }
        if readerFunc != nil {
            skipRest, err := readerFunc(rowLine, valuePlaceholders...)
            if err != nil {
                return err
            }
            if skipRest {
                break
            }
        }
        rowLine++
    }
    if rowLine == 1 {
        return errors.Get(appErrors.ACNoRows)
    }
    return nil
}

Setup

suite.fnStubFalse is simply a function stub that returns false,nil suite.spots is simply an []*interface{} of size 3. Simply put, it's three spots to Scan to.

The rest of the definitions relevant to the test are defined in this helper method which is invoked on suite setup:

func (suite *RowsSuite) setupRowStates() {
    // for throwing fatal error right away
    t := suite.T()
    // fire up the mock
    suite.db, suite.mock, suite.err = sqlmock.New()
    // if there's an error, fatally throw it right away!
    if !assert.Nilf(t,
        suite.err,
        "Error with initializing a stub database connection") {
        t.Fatal()
    }

    // define the possible expectant result sets
    noRows := sqlmock.NewRows(suite.columns)
    nonEmptyRows := sqlmock.NewRows(suite.columns).
        AddRow(381, "Beans", 1.59).
        AddRow(34981, "Frozen Pizza", 5.49)

    // define our sql behavior
    regex := "^SELECT (.+) FROM items$"
    sql := "SELECT (item_id, item_name, item_price) FROM items"

    specificRegex := "^SELECT (.+) FROM items (.+)$"
    specificSQL := `
    SELECT (item_id, item_name, item_price) FROM items i
    INNER JOIN stock s
    ON s.item_id = i.item_id
    WHERE TIME_TO_SEC(s.stock_time) > TIME_TO_SEC(NOW())`

    // setup general query to return non-empty rows
    suite.mock.ExpectQuery(regex).
        WillReturnRows(nonEmptyRows)
    // setup specific query to return empty rows
    suite.mock.ExpectQuery(specificRegex).
        WillReturnRows(noRows)

    // hit both queries right now and store the state of their
    //  return values, terminating right away on any errors
    var err error
    rows, err := suite.db.Query(sql)
    if err != nil {
        t.Fatal(err.Error())
    }
    suite.nonEmptyRows = *rows
    emptyRows, err := suite.db.Query(specificSQL)
    if err != nil {
        t.Fatal(err.Error())
    }
    suite.noRows = *emptyRows

}

The full error

This monstrosity:

Running tool: C:\Go\bin\go.exe test -timeout 30s ezsoft\apiserver_sdk\db -run ^TestRowsSuite$

panic: sql: connection returned that was never out

goroutine 22 [running]:
database/sql.(*DB).putConn(0xc04204d400, 0xc04212a080, 0x0, 0x0, 0xc04213de01)
    C:/Go/src/database/sql/sql.go:1158 +0x351
database/sql.(*driverConn).releaseConn(0xc04212a080, 0x0, 0x0)
    C:/Go/src/database/sql/sql.go:383 +0x53
database/sql.(*driverConn).(database/sql.releaseConn)-fm(0x0, 0x0)
    C:/Go/src/database/sql/sql.go:706 +0x45
database/sql.(*Rows).close(0xc04212a100, 0x899be0, 0xc042048380, 0x0, 0x0)
    C:/Go/src/database/sql/sql.go:2932 +0x159
database/sql.(*Rows).awaitDone(0xc04212a100, 0x89d260, 0xc042050b00, 0x0, 0x0)
    C:/Go/src/database/sql/sql.go:2588 +0x12f
created by database/sql.(*Rows).initContextClose
    C:/Go/src/database/sql/sql.go:2572 +0xa3
FAIL    ezsoft/apiserver_sdk/db 0.429s
Error: Tests failed.

Third-party libraries used

I use testify and go-sqlmock (I'm probably shooting myself in the foot by using it to simply stub queries, because I'm having to jump through hoops in the setup.)

I have no idea what's causing this fail. When I delete the test, and run the suite itself, everything works

Turns out that I was overthinking this situation.

I fixed it by storing the two row states as pointers, and then setting them in RowsSuite.SetupTest (at the test level, not the suite level). From there, I just fetch one of the states in the test, and I was good to go. Namely, instead of this (which would work fine in C++):

func (suite *RowsSuite) TestReadFrom_SanityTest() {
    t := suite.T()

    rows := new(sql.Rows)
    *rows = suite.nonEmptyRows

    assert.True(t, rows.Next())
    // hit the function like it's supposed to be hit
    err := ReadFrom(rows,
        suite.fnStubFalse,
        suite.spots[0],
        suite.spots[1],
        suite.spots[2])

    // There better be no error
    if !assert.Nil(t, err) {
        t.Error(err.ToString(false))
    }

}

I did this:

func (suite *RowsSuite) TestReadFrom_SanityTest() {
    t := suite.T()

    rows := suite.nonEmptyRows

    // hit the function like it's supposed to be hit
    errSt := ReadFrom(rows,
        suite.fnStubFalse,
        suite.spots[0],
        suite.spots[1],
        suite.spots[2])

    // There better be no error
    if !assert.Nil(t, errSt) {
        t.Error(errSt.ToString(false))
    }

}

where nonEmptyRows is one of the states set just before the test is run