1
0
mirror of https://github.com/DATA-DOG/go-sqlmock.git synced 2025-06-29 00:31:35 +02:00

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)
This commit is contained in:
David Ackroyd
2019-06-21 13:28:10 +10:00
parent 6c8a572d09
commit d5879ee4b7
4 changed files with 486 additions and 0 deletions

View File

@ -3,6 +3,8 @@
package sqlmock
import (
"database/sql"
"encoding/json"
"fmt"
"testing"
)
@ -90,3 +92,114 @@ func TestQueryMultiRows(t *testing.T) {
t.Errorf("there were unfulfilled expectations: %s", err)
}
}
func TestQueryRowBytesInvalidatedByNext_jsonRawMessageIntoRawBytes(t *testing.T) {
t.Parallel()
replace := []byte(invalid)
rows := NewRows([]string{"raw"}).
AddRow(json.RawMessage(`{"thing": "one", "thing2": "two"}`)).
AddRow(json.RawMessage(`{"that": "foo", "this": "bar"}`))
scan := func(rs *sql.Rows) ([]byte, error) {
var raw sql.RawBytes
return raw, rs.Scan(&raw)
}
want := []struct {
Initial []byte
Replaced []byte
}{
{Initial: []byte(`{"thing": "one", "thing2": "two"}`), Replaced: replace[:len(replace)-6]},
{Initial: []byte(`{"that": "foo", "this": "bar"}`), Replaced: replace[:len(replace)-9]},
}
queryRowBytesInvalidatedByNext(t, rows, scan, want)
}
func TestQueryRowBytesNotInvalidatedByNext_jsonRawMessageIntoBytes(t *testing.T) {
t.Parallel()
rows := NewRows([]string{"raw"}).
AddRow(json.RawMessage(`{"thing": "one", "thing2": "two"}`)).
AddRow(json.RawMessage(`{"that": "foo", "this": "bar"}`))
scan := func(rs *sql.Rows) ([]byte, error) {
var b []byte
return b, rs.Scan(&b)
}
want := [][]byte{[]byte(`{"thing": "one", "thing2": "two"}`), []byte(`{"that": "foo", "this": "bar"}`)}
queryRowBytesNotInvalidatedByNext(t, rows, scan, want)
}
func TestQueryRowBytesNotInvalidatedByNext_bytesIntoCustomBytes(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) {
type customBytes []byte
var b customBytes
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_jsonRawMessageIntoCustomBytes(t *testing.T) {
t.Parallel()
rows := NewRows([]string{"raw"}).
AddRow(json.RawMessage(`{"thing": "one", "thing2": "two"}`)).
AddRow(json.RawMessage(`{"that": "foo", "this": "bar"}`))
scan := func(rs *sql.Rows) ([]byte, error) {
type customBytes []byte
var b customBytes
return b, rs.Scan(&b)
}
want := [][]byte{[]byte(`{"thing": "one", "thing2": "two"}`), []byte(`{"that": "foo", "this": "bar"}`)}
queryRowBytesNotInvalidatedByNext(t, rows, scan, want)
}
func TestQueryRowBytesNotInvalidatedByClose_bytesIntoCustomBytes(t *testing.T) {
t.Parallel()
rows := NewRows([]string{"raw"}).AddRow([]byte(`one binary value with some text!`))
scan := func(rs *sql.Rows) ([]byte, error) {
type customBytes []byte
var b customBytes
return b, rs.Scan(&b)
}
queryRowBytesNotInvalidatedByClose(t, rows, scan, []byte(`one binary value with some text!`))
}
func TestQueryRowBytesInvalidatedByClose_jsonRawMessageIntoRawBytes(t *testing.T) {
t.Parallel()
replace := []byte(invalid)
rows := NewRows([]string{"raw"}).AddRow(json.RawMessage(`{"thing": "one", "thing2": "two"}`))
scan := func(rs *sql.Rows) ([]byte, error) {
var raw sql.RawBytes
return raw, rs.Scan(&raw)
}
want := struct {
Initial []byte
Replaced []byte
}{
Initial: []byte(`{"thing": "one", "thing2": "two"}`),
Replaced: replace[:len(replace)-6],
}
queryRowBytesInvalidatedByClose(t, rows, scan, want)
}
func TestQueryRowBytesNotInvalidatedByClose_jsonRawMessageIntoBytes(t *testing.T) {
t.Parallel()
rows := NewRows([]string{"raw"}).AddRow(json.RawMessage(`{"thing": "one", "thing2": "two"}`))
scan := func(rs *sql.Rows) ([]byte, error) {
var b []byte
return b, rs.Scan(&b)
}
queryRowBytesNotInvalidatedByClose(t, rows, scan, []byte(`{"thing": "one", "thing2": "two"}`))
}
func TestQueryRowBytesNotInvalidatedByClose_jsonRawMessageIntoCustomBytes(t *testing.T) {
t.Parallel()
rows := NewRows([]string{"raw"}).AddRow(json.RawMessage(`{"thing": "one", "thing2": "two"}`))
scan := func(rs *sql.Rows) ([]byte, error) {
type customBytes []byte
var b customBytes
return b, rs.Scan(&b)
}
queryRowBytesNotInvalidatedByClose(t, rows, scan, []byte(`{"thing": "one", "thing2": "two"}`))
}