1
0
mirror of https://github.com/zhashkevych/go-sqlxmock.git synced 2024-11-16 17:41:57 +02:00
go-sqlxmock/rows_test.go
David Ackroyd d5879ee4b7 Invalidate memory scanned into sql.RawBytes
The intention of sql.RawBytes is for it to hold memory owned by the
 database. When used, it's content is only valid until the `Next`,
 `Scan` or `Close` is called on the `Rows`

To ensure that we meet this behaviour, when `[]byte` is used in a
 column, it's value is copied to a buffer that we keep track of for
 later invalidation. By doing this, incorrect use of `sql.RawBytes`
 values is exposed in tests that use go-sqlmock. Without this, when a
 real database is used and it's driver does share memory, then those
 issues would not be exposed until runtime (and in non-obvious ways)
2019-06-21 17:03:05 +10:00

673 lines
18 KiB
Go

package sqlmock
import (
"bytes"
"database/sql"
"fmt"
"testing"
)
const invalid = `☠☠☠ MEMORY OVERWRITTEN ☠☠☠ `
func ExampleRows() {
db, mock, err := New()
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
rows := NewRows([]string{"id", "title"}).
AddRow(1, "one").
AddRow(2, "two")
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, _ := db.Query("SELECT")
defer rs.Close()
for rs.Next() {
var id int
var title string
rs.Scan(&id, &title)
fmt.Println("scanned id:", id, "and title:", title)
}
if rs.Err() != nil {
fmt.Println("got rows error:", rs.Err())
}
// Output: scanned id: 1 and title: one
// scanned id: 2 and title: two
}
func ExampleRows_rowError() {
db, mock, err := New()
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
rows := NewRows([]string{"id", "title"}).
AddRow(0, "one").
AddRow(1, "two").
RowError(1, fmt.Errorf("row error"))
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, _ := db.Query("SELECT")
defer rs.Close()
for rs.Next() {
var id int
var title string
rs.Scan(&id, &title)
fmt.Println("scanned id:", id, "and title:", title)
}
if rs.Err() != nil {
fmt.Println("got rows error:", rs.Err())
}
// Output: scanned id: 0 and title: one
// got rows error: row error
}
func ExampleRows_closeError() {
db, mock, err := New()
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
rows := NewRows([]string{"id", "title"}).CloseError(fmt.Errorf("close error"))
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, _ := db.Query("SELECT")
// Note: that close will return error only before rows EOF
// that is a default sql package behavior. If you run rs.Next()
// it will handle the error internally and return nil bellow
if err := rs.Close(); err != nil {
fmt.Println("got error:", err)
}
// Output: got error: close error
}
func ExampleRows_rawBytes() {
db, mock, err := New()
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
rows := NewRows([]string{"id", "binary"}).
AddRow(1, []byte(`one binary value with some text!`)).
AddRow(2, []byte(`two binary value with even more text than the first one`))
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, _ := db.Query("SELECT")
defer rs.Close()
type scanned struct {
id int
raw sql.RawBytes
}
fmt.Println("initial read...")
var ss []scanned
for rs.Next() {
var s scanned
rs.Scan(&s.id, &s.raw)
ss = append(ss, s)
fmt.Println("scanned id:", s.id, "and raw:", string(s.raw))
}
if rs.Err() != nil {
fmt.Println("got rows error:", rs.Err())
}
fmt.Println("after reading all...")
for _, s := range ss {
fmt.Println("scanned id:", s.id, "and raw:", string(s.raw))
}
// Output:
// initial read...
// scanned id: 1 and raw: one binary value with some text!
// scanned id: 2 and raw: two binary value with even more text than the first one
// after reading all...
// scanned id: 1 and raw: ☠☠☠ MEMORY OVERWRITTEN ☠
// scanned id: 2 and raw: ☠☠☠ MEMORY OVERWRITTEN ☠☠☠ ☠☠☠ MEMORY
}
func ExampleRows_expectToBeClosed() {
db, mock, err := New()
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
rows := NewRows([]string{"id", "title"}).AddRow(1, "john")
mock.ExpectQuery("SELECT").WillReturnRows(rows).RowsWillBeClosed()
db.Query("SELECT")
if err := mock.ExpectationsWereMet(); err != nil {
fmt.Println("got error:", err)
}
// Output: got error: expected query rows to be closed, but it was not: ExpectedQuery => expecting Query, QueryContext or QueryRow which:
// - matches sql: 'SELECT'
// - is without arguments
// - should return rows:
// row 0 - [1 john]
}
func ExampleRows_customDriverValue() {
db, mock, err := New()
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
rows := NewRows([]string{"id", "null_int"}).
AddRow(1, 7).
AddRow(5, sql.NullInt64{Int64: 5, Valid: true}).
AddRow(2, sql.NullInt64{})
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, _ := db.Query("SELECT")
defer rs.Close()
for rs.Next() {
var id int
var num sql.NullInt64
rs.Scan(&id, &num)
fmt.Println("scanned id:", id, "and null int64:", num)
}
if rs.Err() != nil {
fmt.Println("got rows error:", rs.Err())
}
// Output: scanned id: 1 and null int64: {7 true}
// scanned id: 5 and null int64: {5 true}
// scanned id: 2 and null int64: {0 false}
}
func TestAllowsToSetRowsErrors(t *testing.T) {
t.Parallel()
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
rows := NewRows([]string{"id", "title"}).
AddRow(0, "one").
AddRow(1, "two").
RowError(1, fmt.Errorf("error"))
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, err := db.Query("SELECT")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer rs.Close()
if !rs.Next() {
t.Fatal("expected the first row to be available")
}
if rs.Err() != nil {
t.Fatalf("unexpected error: %s", rs.Err())
}
if rs.Next() {
t.Fatal("was not expecting the second row, since there should be an error")
}
if rs.Err() == nil {
t.Fatal("expected an error, but got none")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func TestRowsCloseError(t *testing.T) {
t.Parallel()
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
rows := NewRows([]string{"id"}).CloseError(fmt.Errorf("close error"))
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, err := db.Query("SELECT")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if err := rs.Close(); err == nil {
t.Fatal("expected a close error")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func TestRowsClosed(t *testing.T) {
t.Parallel()
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
rows := NewRows([]string{"id"}).AddRow(1)
mock.ExpectQuery("SELECT").WillReturnRows(rows).RowsWillBeClosed()
rs, err := db.Query("SELECT")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if err := rs.Close(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func TestQuerySingleRow(t *testing.T) {
t.Parallel()
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
rows := NewRows([]string{"id"}).
AddRow(1).
AddRow(2)
mock.ExpectQuery("SELECT").WillReturnRows(rows)
var id int
if err := db.QueryRow("SELECT").Scan(&id); err != nil {
t.Fatalf("unexpected error: %s", err)
}
mock.ExpectQuery("SELECT").WillReturnRows(NewRows([]string{"id"}))
if err := db.QueryRow("SELECT").Scan(&id); err != sql.ErrNoRows {
t.Fatal("expected sql no rows error")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func TestQueryRowBytesInvalidatedByNext_bytesIntoRawBytes(t *testing.T) {
t.Parallel()
replace := []byte(invalid)
rows := NewRows([]string{"raw"}).
AddRow([]byte(`one binary value with some text!`)).
AddRow([]byte(`two binary value with even more text than the first one`))
scan := func(rs *sql.Rows) ([]byte, error) {
var raw sql.RawBytes
return raw, rs.Scan(&raw)
}
want := []struct {
Initial []byte
Replaced []byte
}{
{Initial: []byte(`one binary value with some text!`), Replaced: replace[:len(replace)-7]},
{Initial: []byte(`two binary value with even more text than the first one`), Replaced: bytes.Join([][]byte{replace, replace[:len(replace)-23]}, nil)},
}
queryRowBytesInvalidatedByNext(t, rows, scan, want)
}
func TestQueryRowBytesNotInvalidatedByNext_bytesIntoBytes(t *testing.T) {
t.Parallel()
rows := NewRows([]string{"raw"}).
AddRow([]byte(`one binary value with some text!`)).
AddRow([]byte(`two binary value with even more text than the first one`))
scan := func(rs *sql.Rows) ([]byte, error) {
var b []byte
return b, rs.Scan(&b)
}
want := [][]byte{[]byte(`one binary value with some text!`), []byte(`two binary value with even more text than the first one`)}
queryRowBytesNotInvalidatedByNext(t, rows, scan, want)
}
func TestQueryRowBytesNotInvalidatedByNext_stringIntoBytes(t *testing.T) {
t.Parallel()
rows := NewRows([]string{"raw"}).
AddRow(`one binary value with some text!`).
AddRow(`two binary value with even more text than the first one`)
scan := func(rs *sql.Rows) ([]byte, error) {
var b []byte
return b, rs.Scan(&b)
}
want := [][]byte{[]byte(`one binary value with some text!`), []byte(`two binary value with even more text than the first one`)}
queryRowBytesNotInvalidatedByNext(t, rows, scan, want)
}
func TestQueryRowBytesInvalidatedByClose_bytesIntoRawBytes(t *testing.T) {
t.Parallel()
replace := []byte(invalid)
rows := NewRows([]string{"raw"}).AddRow([]byte(`one binary value with some text!`))
scan := func(rs *sql.Rows) ([]byte, error) {
var raw sql.RawBytes
return raw, rs.Scan(&raw)
}
want := struct {
Initial []byte
Replaced []byte
}{
Initial: []byte(`one binary value with some text!`),
Replaced: replace[:len(replace)-7],
}
queryRowBytesInvalidatedByClose(t, rows, scan, want)
}
func TestQueryRowBytesNotInvalidatedByClose_bytesIntoBytes(t *testing.T) {
t.Parallel()
rows := NewRows([]string{"raw"}).AddRow([]byte(`one binary value with some text!`))
scan := func(rs *sql.Rows) ([]byte, error) {
var b []byte
return b, rs.Scan(&b)
}
queryRowBytesNotInvalidatedByClose(t, rows, scan, []byte(`one binary value with some text!`))
}
func TestQueryRowBytesNotInvalidatedByClose_stringIntoBytes(t *testing.T) {
t.Parallel()
rows := NewRows([]string{"raw"}).AddRow(`one binary value with some text!`)
scan := func(rs *sql.Rows) ([]byte, error) {
var b []byte
return b, rs.Scan(&b)
}
queryRowBytesNotInvalidatedByClose(t, rows, scan, []byte(`one binary value with some text!`))
}
func TestRowsScanError(t *testing.T) {
t.Parallel()
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
r := NewRows([]string{"col1", "col2"}).AddRow("one", "two").AddRow("one", nil)
mock.ExpectQuery("SELECT").WillReturnRows(r)
rs, err := db.Query("SELECT")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer rs.Close()
var one, two string
if !rs.Next() || rs.Err() != nil || rs.Scan(&one, &two) != nil {
t.Fatal("unexpected error on first row scan")
}
if !rs.Next() || rs.Err() != nil {
t.Fatal("unexpected error on second row read")
}
err = rs.Scan(&one, &two)
if err == nil {
t.Fatal("expected an error for scan, but got none")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func TestCSVRowParser(t *testing.T) {
t.Parallel()
rs := NewRows([]string{"col1", "col2"}).FromCSVString("a,NULL")
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
mock.ExpectQuery("SELECT").WillReturnRows(rs)
rw, err := db.Query("SELECT")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer rw.Close()
var col1 string
var col2 []byte
rw.Next()
if err = rw.Scan(&col1, &col2); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if col1 != "a" {
t.Fatalf("expected col1 to be 'a', but got [%T]:%+v", col1, col1)
}
if col2 != nil {
t.Fatalf("expected col2 to be nil, but got [%T]:%+v", col2, col2)
}
}
func TestWrongNumberOfValues(t *testing.T) {
// Open new mock database
db, mock, err := New()
if err != nil {
fmt.Println("error creating mock database")
return
}
defer db.Close()
defer func() {
recover()
}()
mock.ExpectQuery("SELECT ID FROM TABLE").WithArgs(101).WillReturnRows(NewRows([]string{"ID"}).AddRow(101, "Hello"))
db.Query("SELECT ID FROM TABLE", 101)
// shouldn't reach here
t.Error("expected panic from query")
}
func TestEmptyRowSets(t *testing.T) {
rs1 := NewRows([]string{"a"}).AddRow("a")
rs2 := NewRows([]string{"b"})
rs3 := NewRows([]string{"c"})
set1 := &rowSets{sets: []*Rows{rs1, rs2}}
set2 := &rowSets{sets: []*Rows{rs3, rs2}}
set3 := &rowSets{sets: []*Rows{rs2}}
if set1.empty() {
t.Fatalf("expected rowset 1, not to be empty, but it was")
}
if !set2.empty() {
t.Fatalf("expected rowset 2, to be empty, but it was not")
}
if !set3.empty() {
t.Fatalf("expected rowset 3, to be empty, but it was not")
}
}
func queryRowBytesInvalidatedByNext(t *testing.T, rows *Rows, scan func(*sql.Rows) ([]byte, error), want []struct {
Initial []byte
Replaced []byte
}) {
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, err := db.Query("SELECT")
if err != nil {
t.Fatalf("failed to query rows: %s", err)
}
if !rs.Next() || rs.Err() != nil {
t.Fatal("unexpected error on first row retrieval")
}
var count int
for i := 0; ; i++ {
count++
b, err := scan(rs)
if err != nil {
t.Fatalf("unexpected error scanning row: %s", err)
}
if exp := want[i].Initial; !bytes.Equal(b, exp) {
t.Fatalf("expected raw value to be '%s' (len:%d), but got [%T]:%s (len:%d)", exp, len(exp), b, b, len(b))
}
next := rs.Next()
if exp := want[i].Replaced; !bytes.Equal(b, exp) {
t.Fatalf("expected raw value to be replaced with '%s' (len:%d) after calling Next(), but got [%T]:%s (len:%d)", exp, len(exp), b, b, len(b))
}
if !next {
break
}
}
if err := rs.Err(); err != nil {
t.Fatalf("row iteration failed: %s", err)
}
if exp := len(want); count != exp {
t.Fatalf("incorrect number of rows exp: %d, but got %d", exp, count)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func queryRowBytesNotInvalidatedByNext(t *testing.T, rows *Rows, scan func(*sql.Rows) ([]byte, error), want [][]byte) {
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, err := db.Query("SELECT")
if err != nil {
t.Fatalf("failed to query rows: %s", err)
}
if !rs.Next() || rs.Err() != nil {
t.Fatal("unexpected error on first row retrieval")
}
var count int
for i := 0; ; i++ {
count++
b, err := scan(rs)
if err != nil {
t.Fatalf("unexpected error scanning row: %s", err)
}
if exp := want[i]; !bytes.Equal(b, exp) {
t.Fatalf("expected raw value to be '%s' (len:%d), but got [%T]:%s (len:%d)", exp, len(exp), b, b, len(b))
}
next := rs.Next()
if exp := want[i]; !bytes.Equal(b, exp) {
t.Fatalf("expected raw value to be replaced with '%s' (len:%d) after calling Next(), but got [%T]:%s (len:%d)", exp, len(exp), b, b, len(b))
}
if !next {
break
}
}
if err := rs.Err(); err != nil {
t.Fatalf("row iteration failed: %s", err)
}
if exp := len(want); count != exp {
t.Fatalf("incorrect number of rows exp: %d, but got %d", exp, count)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func queryRowBytesInvalidatedByClose(t *testing.T, rows *Rows, scan func(*sql.Rows) ([]byte, error), want struct {
Initial []byte
Replaced []byte
}) {
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, err := db.Query("SELECT")
if err != nil {
t.Fatalf("failed to query rows: %s", err)
}
if !rs.Next() || rs.Err() != nil {
t.Fatal("unexpected error on first row retrieval")
}
b, err := scan(rs)
if err != nil {
t.Fatalf("unexpected error scanning row: %s", err)
}
if !bytes.Equal(b, want.Initial) {
t.Fatalf("expected raw value to be '%s' (len:%d), but got [%T]:%s (len:%d)", want.Initial, len(want.Initial), b, b, len(b))
}
if err := rs.Close(); err != nil {
t.Fatalf("unexpected error closing rows: %s", err)
}
if !bytes.Equal(b, want.Replaced) {
t.Fatalf("expected raw value to be replaced with '%s' (len:%d) after calling Next(), but got [%T]:%s (len:%d)", want.Replaced, len(want.Replaced), b, b, len(b))
}
if err := rs.Err(); err != nil {
t.Fatalf("row iteration failed: %s", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func queryRowBytesNotInvalidatedByClose(t *testing.T, rows *Rows, scan func(*sql.Rows) ([]byte, error), want []byte) {
db, mock, err := New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, err := db.Query("SELECT")
if err != nil {
t.Fatalf("failed to query rows: %s", err)
}
if !rs.Next() || rs.Err() != nil {
t.Fatal("unexpected error on first row retrieval")
}
b, err := scan(rs)
if err != nil {
t.Fatalf("unexpected error scanning row: %s", err)
}
if !bytes.Equal(b, want) {
t.Fatalf("expected raw value to be '%s' (len:%d), but got [%T]:%s (len:%d)", want, len(want), b, b, len(b))
}
if err := rs.Close(); err != nil {
t.Fatalf("unexpected error closing rows: %s", err)
}
if !bytes.Equal(b, want) {
t.Fatalf("expected raw value to be replaced with '%s' (len:%d) after calling Next(), but got [%T]:%s (len:%d)", want, len(want), b, b, len(b))
}
if err := rs.Err(); err != nil {
t.Fatalf("row iteration failed: %s", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}