Commit 18ff1569 by Torkel Ödegaard

Added missing mysql driver package dependency

parent 3226a3a5
......@@ -20,6 +20,11 @@
"Rev": "9908e96513e5a94de37004098a3974a567f18111"
},
{
"ImportPath": "github.com/go-sql-driver/mysql",
"Comment": "v1.2-26-g9543750",
"Rev": "9543750295406ef070f7de8ae9c43ccddd44e15e"
},
{
"ImportPath": "github.com/go-xorm/core",
"Rev": "a949e067ced1cb6e6ef5c38b6f28b074fa718f1e"
},
......@@ -37,13 +42,17 @@
"Rev": "d10e2c8f62100097910367dee90a9bd89d426a44"
},
{
"ImportPath": "github.com/smartystreets/goconvey/convey",
"Comment": "1.5.0-254-g627707e",
"Rev": "627707e8db4a4e52f4e1fbbb4e10d98e79a3c946"
"ImportPath": "golang.org/x/net/context",
"Rev": "972f0c5fbe4ae29e666c3f78c3ed42ae7a448b0a"
},
{
"ImportPath": "golang.org/x/oauth2",
"Rev": "e5909d4679a1926c774c712b343f10b8298687a3"
},
{
"ImportPath": "gopkg.in/ini.v1",
"Comment": "v0-10-g28ad8c4",
"Rev": "28ad8c408ba20e5c86b06d64cd2cc9248f640a83"
}
]
}
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
Icon?
ehthumbs.db
Thumbs.db
language: go
go:
- 1.1
- 1.2
- 1.3
- tip
before_script:
- mysql -e 'create database gotest;'
# This is the official list of Go-MySQL-Driver authors for copyright purposes.
# If you are submitting a patch, please add your name or the name of the
# organization which holds the copyright to this list in alphabetical order.
# Names should be added to this file as
# Name <email address>
# The email address is not required for organizations.
# Please keep the list sorted.
# Individual Persons
Aaron Hopkins <go-sql-driver at die.net>
Arne Hormann <arnehormann at gmail.com>
Carlos Nieto <jose.carlos at menteslibres.net>
DisposaBoy <disposaboy at dby.me>
Frederick Mayle <frederickmayle at gmail.com>
Gustavo Kristic <gkristic at gmail.com>
Hanno Braun <mail at hannobraun.com>
Henri Yandell <flamefew at gmail.com>
James Harr <james.harr at gmail.com>
Jian Zhen <zhenjl at gmail.com>
Julien Schmidt <go-sql-driver at julienschmidt.com>
Leonardo YongUk Kim <dalinaum at gmail.com>
Lucas Liu <extrafliu at gmail.com>
Luke Scott <luke at webconnex.com>
Michael Woolnough <michael.woolnough at gmail.com>
Nicola Peduzzi <thenikso at gmail.com>
Xiaobing Jiang <s7v7nislands at gmail.com>
Xiuming Chen <cc at cxm.cc>
# Organizations
Barracuda Networks, Inc.
Google Inc.
## HEAD
Changes:
- Use decimals field from MySQL to format time types
Bugfixes:
- Enable microsecond resolution on TIME, DATETIME and TIMESTAMP
## Version 1.2 (2014-06-03)
Changes:
- We switched back to a "rolling release". `go get` installs the current master branch again
- Version v1 of the driver will not be maintained anymore. Go 1.0 is no longer supported by this driver
- Exported errors to allow easy checking from application code
- Enabled TCP Keepalives on TCP connections
- Optimized INFILE handling (better buffer size calculation, lazy init, ...)
- The DSN parser also checks for a missing separating slash
- Faster binary date / datetime to string formatting
- Also exported the MySQLWarning type
- mysqlConn.Close returns the first error encountered instead of ignoring all errors
- writePacket() automatically writes the packet size to the header
- readPacket() uses an iterative approach instead of the recursive approach to merge splitted packets
New Features:
- `RegisterDial` allows the usage of a custom dial function to establish the network connection
- Setting the connection collation is possible with the `collation` DSN parameter. This parameter should be preferred over the `charset` parameter
- Logging of critical errors is configurable with `SetLogger`
- Google CloudSQL support
Bugfixes:
- Allow more than 32 parameters in prepared statements
- Various old_password fixes
- Fixed TestConcurrent test to pass Go's race detection
- Fixed appendLengthEncodedInteger for large numbers
- Renamed readLengthEnodedString to readLengthEncodedString and skipLengthEnodedString to skipLengthEncodedString (fixed typo)
## Version 1.1 (2013-11-02)
Changes:
- Go-MySQL-Driver now requires Go 1.1
- Connections now use the collation `utf8_general_ci` by default. Adding `&charset=UTF8` to the DSN should not be necessary anymore
- Made closing rows and connections error tolerant. This allows for example deferring rows.Close() without checking for errors
- `[]byte(nil)` is now treated as a NULL value. Before, it was treated like an empty string / `[]byte("")`
- DSN parameter values must now be url.QueryEscape'ed. This allows text values to contain special characters, such as '&'.
- Use the IO buffer also for writing. This results in zero allocations (by the driver) for most queries
- Optimized the buffer for reading
- stmt.Query now caches column metadata
- New Logo
- Changed the copyright header to include all contributors
- Improved the LOAD INFILE documentation
- The driver struct is now exported to make the driver directly accessible
- Refactored the driver tests
- Added more benchmarks and moved all to a separate file
- Other small refactoring
New Features:
- Added *old_passwords* support: Required in some cases, but must be enabled by adding `allowOldPasswords=true` to the DSN since it is insecure
- Added a `clientFoundRows` parameter: Return the number of matching rows instead of the number of rows changed on UPDATEs
- Added TLS/SSL support: Use a TLS/SSL encrypted connection to the server. Custom TLS configs can be registered and used
Bugfixes:
- Fixed MySQL 4.1 support: MySQL 4.1 sends packets with lengths which differ from the specification
- Convert to DB timezone when inserting `time.Time`
- Splitted packets (more than 16MB) are now merged correctly
- Fixed false positive `io.EOF` errors when the data was fully read
- Avoid panics on reuse of closed connections
- Fixed empty string producing false nil values
- Fixed sign byte for positive TIME fields
## Version 1.0 (2013-05-14)
Initial Release
# Contributing Guidelines
## Reporting Issues
Before creating a new Issue, please check first if a similar Issue [already exists](https://github.com/go-sql-driver/mysql/issues?state=open) or was [recently closed](https://github.com/go-sql-driver/mysql/issues?direction=desc&page=1&sort=updated&state=closed).
Please provide the following minimum information:
* Your Go-MySQL-Driver version (or git SHA)
* Your Go version (run `go version` in your console)
* A detailed issue description
* Error Log if present
* If possible, a short example
## Contributing Code
By contributing to this project, you share your code under the Mozilla Public License 2, as specified in the LICENSE file.
Don't forget to add yourself to the AUTHORS file.
### Pull Requests Checklist
Please check the following points before submitting your pull request:
- [x] Code compiles correctly
- [x] Created tests, if possible
- [x] All tests pass
- [x] Extended the README / documentation, if necessary
- [x] Added yourself to the AUTHORS file
### Code Review
Everyone is invited to review and comment on pull requests.
If it looks fine to you, comment with "LGTM" (Looks good to me).
If changes are required, notice the reviewers with "PTAL" (Please take another look) after committing the fixes.
Before merging the Pull Request, at least one [team member](https://github.com/go-sql-driver?tab=members) must have commented with "LGTM".
## Development Ideas
If you are looking for ideas for code contributions, please check our [Development Ideas](https://github.com/go-sql-driver/mysql/wiki/Development-Ideas) Wiki page.
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build appengine
package mysql
import (
"appengine/cloudsql"
)
func init() {
RegisterDial("cloudsql", cloudsql.Dial)
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"bytes"
"database/sql"
"strings"
"sync"
"sync/atomic"
"testing"
)
type TB testing.B
func (tb *TB) check(err error) {
if err != nil {
tb.Fatal(err)
}
}
func (tb *TB) checkDB(db *sql.DB, err error) *sql.DB {
tb.check(err)
return db
}
func (tb *TB) checkRows(rows *sql.Rows, err error) *sql.Rows {
tb.check(err)
return rows
}
func (tb *TB) checkStmt(stmt *sql.Stmt, err error) *sql.Stmt {
tb.check(err)
return stmt
}
func initDB(b *testing.B, queries ...string) *sql.DB {
tb := (*TB)(b)
db := tb.checkDB(sql.Open("mysql", dsn))
for _, query := range queries {
if _, err := db.Exec(query); err != nil {
b.Fatalf("Error on %q: %v", query, err)
}
}
return db
}
const concurrencyLevel = 10
func BenchmarkQuery(b *testing.B) {
tb := (*TB)(b)
b.StopTimer()
b.ReportAllocs()
db := initDB(b,
"DROP TABLE IF EXISTS foo",
"CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))",
`INSERT INTO foo VALUES (1, "one")`,
`INSERT INTO foo VALUES (2, "two")`,
)
db.SetMaxIdleConns(concurrencyLevel)
defer db.Close()
stmt := tb.checkStmt(db.Prepare("SELECT val FROM foo WHERE id=?"))
defer stmt.Close()
remain := int64(b.N)
var wg sync.WaitGroup
wg.Add(concurrencyLevel)
defer wg.Wait()
b.StartTimer()
for i := 0; i < concurrencyLevel; i++ {
go func() {
for {
if atomic.AddInt64(&remain, -1) < 0 {
wg.Done()
return
}
var got string
tb.check(stmt.QueryRow(1).Scan(&got))
if got != "one" {
b.Errorf("query = %q; want one", got)
wg.Done()
return
}
}
}()
}
}
func BenchmarkExec(b *testing.B) {
tb := (*TB)(b)
b.StopTimer()
b.ReportAllocs()
db := tb.checkDB(sql.Open("mysql", dsn))
db.SetMaxIdleConns(concurrencyLevel)
defer db.Close()
stmt := tb.checkStmt(db.Prepare("DO 1"))
defer stmt.Close()
remain := int64(b.N)
var wg sync.WaitGroup
wg.Add(concurrencyLevel)
defer wg.Wait()
b.StartTimer()
for i := 0; i < concurrencyLevel; i++ {
go func() {
for {
if atomic.AddInt64(&remain, -1) < 0 {
wg.Done()
return
}
if _, err := stmt.Exec(); err != nil {
b.Fatal(err.Error())
}
}
}()
}
}
// data, but no db writes
var roundtripSample []byte
func initRoundtripBenchmarks() ([]byte, int, int) {
if roundtripSample == nil {
roundtripSample = []byte(strings.Repeat("0123456789abcdef", 1024*1024))
}
return roundtripSample, 16, len(roundtripSample)
}
func BenchmarkRoundtripTxt(b *testing.B) {
b.StopTimer()
sample, min, max := initRoundtripBenchmarks()
sampleString := string(sample)
b.ReportAllocs()
tb := (*TB)(b)
db := tb.checkDB(sql.Open("mysql", dsn))
defer db.Close()
b.StartTimer()
var result string
for i := 0; i < b.N; i++ {
length := min + i
if length > max {
length = max
}
test := sampleString[0:length]
rows := tb.checkRows(db.Query(`SELECT "` + test + `"`))
if !rows.Next() {
rows.Close()
b.Fatalf("crashed")
}
err := rows.Scan(&result)
if err != nil {
rows.Close()
b.Fatalf("crashed")
}
if result != test {
rows.Close()
b.Errorf("mismatch")
}
rows.Close()
}
}
func BenchmarkRoundtripBin(b *testing.B) {
b.StopTimer()
sample, min, max := initRoundtripBenchmarks()
b.ReportAllocs()
tb := (*TB)(b)
db := tb.checkDB(sql.Open("mysql", dsn))
defer db.Close()
stmt := tb.checkStmt(db.Prepare("SELECT ?"))
defer stmt.Close()
b.StartTimer()
var result sql.RawBytes
for i := 0; i < b.N; i++ {
length := min + i
if length > max {
length = max
}
test := sample[0:length]
rows := tb.checkRows(stmt.Query(test))
if !rows.Next() {
rows.Close()
b.Fatalf("crashed")
}
err := rows.Scan(&result)
if err != nil {
rows.Close()
b.Fatalf("crashed")
}
if !bytes.Equal(result, test) {
rows.Close()
b.Errorf("mismatch")
}
rows.Close()
}
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import "io"
const defaultBufSize = 4096
// A buffer which is used for both reading and writing.
// This is possible since communication on each connection is synchronous.
// In other words, we can't write and read simultaneously on the same connection.
// The buffer is similar to bufio.Reader / Writer but zero-copy-ish
// Also highly optimized for this particular use case.
type buffer struct {
buf []byte
rd io.Reader
idx int
length int
}
func newBuffer(rd io.Reader) buffer {
var b [defaultBufSize]byte
return buffer{
buf: b[:],
rd: rd,
}
}
// fill reads into the buffer until at least _need_ bytes are in it
func (b *buffer) fill(need int) error {
n := b.length
// move existing data to the beginning
if n > 0 && b.idx > 0 {
copy(b.buf[0:n], b.buf[b.idx:])
}
// grow buffer if necessary
// TODO: let the buffer shrink again at some point
// Maybe keep the org buf slice and swap back?
if need > len(b.buf) {
// Round up to the next multiple of the default size
newBuf := make([]byte, ((need/defaultBufSize)+1)*defaultBufSize)
copy(newBuf, b.buf)
b.buf = newBuf
}
b.idx = 0
for {
nn, err := b.rd.Read(b.buf[n:])
n += nn
switch err {
case nil:
if n < need {
continue
}
b.length = n
return nil
case io.EOF:
if n >= need {
b.length = n
return nil
}
return io.ErrUnexpectedEOF
default:
return err
}
}
}
// returns next N bytes from buffer.
// The returned slice is only guaranteed to be valid until the next read
func (b *buffer) readNext(need int) ([]byte, error) {
if b.length < need {
// refill
if err := b.fill(need); err != nil {
return nil, err
}
}
offset := b.idx
b.idx += need
b.length -= need
return b.buf[offset:b.idx], nil
}
// returns a buffer with the requested size.
// If possible, a slice from the existing buffer is returned.
// Otherwise a bigger buffer is made.
// Only one buffer (total) can be used at a time.
func (b *buffer) takeBuffer(length int) []byte {
if b.length > 0 {
return nil
}
// test (cheap) general case first
if length <= defaultBufSize || length <= cap(b.buf) {
return b.buf[:length]
}
if length < maxPacketSize {
b.buf = make([]byte, length)
return b.buf
}
return make([]byte, length)
}
// shortcut which can be used if the requested buffer is guaranteed to be
// smaller than defaultBufSize
// Only one buffer (total) can be used at a time.
func (b *buffer) takeSmallBuffer(length int) []byte {
if b.length == 0 {
return b.buf[:length]
}
return nil
}
// takeCompleteBuffer returns the complete existing buffer.
// This can be used if the necessary buffer size is unknown.
// Only one buffer (total) can be used at a time.
func (b *buffer) takeCompleteBuffer() []byte {
if b.length == 0 {
return b.buf
}
return nil
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2014 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
const defaultCollation byte = 33 // utf8_general_ci
// A list of available collations mapped to the internal ID.
// To update this map use the following MySQL query:
// SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS
var collations = map[string]byte{
"big5_chinese_ci": 1,
"latin2_czech_cs": 2,
"dec8_swedish_ci": 3,
"cp850_general_ci": 4,
"latin1_german1_ci": 5,
"hp8_english_ci": 6,
"koi8r_general_ci": 7,
"latin1_swedish_ci": 8,
"latin2_general_ci": 9,
"swe7_swedish_ci": 10,
"ascii_general_ci": 11,
"ujis_japanese_ci": 12,
"sjis_japanese_ci": 13,
"cp1251_bulgarian_ci": 14,
"latin1_danish_ci": 15,
"hebrew_general_ci": 16,
"tis620_thai_ci": 18,
"euckr_korean_ci": 19,
"latin7_estonian_cs": 20,
"latin2_hungarian_ci": 21,
"koi8u_general_ci": 22,
"cp1251_ukrainian_ci": 23,
"gb2312_chinese_ci": 24,
"greek_general_ci": 25,
"cp1250_general_ci": 26,
"latin2_croatian_ci": 27,
"gbk_chinese_ci": 28,
"cp1257_lithuanian_ci": 29,
"latin5_turkish_ci": 30,
"latin1_german2_ci": 31,
"armscii8_general_ci": 32,
"utf8_general_ci": 33,
"cp1250_czech_cs": 34,
"ucs2_general_ci": 35,
"cp866_general_ci": 36,
"keybcs2_general_ci": 37,
"macce_general_ci": 38,
"macroman_general_ci": 39,
"cp852_general_ci": 40,
"latin7_general_ci": 41,
"latin7_general_cs": 42,
"macce_bin": 43,
"cp1250_croatian_ci": 44,
"utf8mb4_general_ci": 45,
"utf8mb4_bin": 46,
"latin1_bin": 47,
"latin1_general_ci": 48,
"latin1_general_cs": 49,
"cp1251_bin": 50,
"cp1251_general_ci": 51,
"cp1251_general_cs": 52,
"macroman_bin": 53,
"utf16_general_ci": 54,
"utf16_bin": 55,
"utf16le_general_ci": 56,
"cp1256_general_ci": 57,
"cp1257_bin": 58,
"cp1257_general_ci": 59,
"utf32_general_ci": 60,
"utf32_bin": 61,
"utf16le_bin": 62,
"binary": 63,
"armscii8_bin": 64,
"ascii_bin": 65,
"cp1250_bin": 66,
"cp1256_bin": 67,
"cp866_bin": 68,
"dec8_bin": 69,
"greek_bin": 70,
"hebrew_bin": 71,
"hp8_bin": 72,
"keybcs2_bin": 73,
"koi8r_bin": 74,
"koi8u_bin": 75,
"latin2_bin": 77,
"latin5_bin": 78,
"latin7_bin": 79,
"cp850_bin": 80,
"cp852_bin": 81,
"swe7_bin": 82,
"utf8_bin": 83,
"big5_bin": 84,
"euckr_bin": 85,
"gb2312_bin": 86,
"gbk_bin": 87,
"sjis_bin": 88,
"tis620_bin": 89,
"ucs2_bin": 90,
"ujis_bin": 91,
"geostd8_general_ci": 92,
"geostd8_bin": 93,
"latin1_spanish_ci": 94,
"cp932_japanese_ci": 95,
"cp932_bin": 96,
"eucjpms_japanese_ci": 97,
"eucjpms_bin": 98,
"cp1250_polish_ci": 99,
"utf16_unicode_ci": 101,
"utf16_icelandic_ci": 102,
"utf16_latvian_ci": 103,
"utf16_romanian_ci": 104,
"utf16_slovenian_ci": 105,
"utf16_polish_ci": 106,
"utf16_estonian_ci": 107,
"utf16_spanish_ci": 108,
"utf16_swedish_ci": 109,
"utf16_turkish_ci": 110,
"utf16_czech_ci": 111,
"utf16_danish_ci": 112,
"utf16_lithuanian_ci": 113,
"utf16_slovak_ci": 114,
"utf16_spanish2_ci": 115,
"utf16_roman_ci": 116,
"utf16_persian_ci": 117,
"utf16_esperanto_ci": 118,
"utf16_hungarian_ci": 119,
"utf16_sinhala_ci": 120,
"utf16_german2_ci": 121,
"utf16_croatian_ci": 122,
"utf16_unicode_520_ci": 123,
"utf16_vietnamese_ci": 124,
"ucs2_unicode_ci": 128,
"ucs2_icelandic_ci": 129,
"ucs2_latvian_ci": 130,
"ucs2_romanian_ci": 131,
"ucs2_slovenian_ci": 132,
"ucs2_polish_ci": 133,
"ucs2_estonian_ci": 134,
"ucs2_spanish_ci": 135,
"ucs2_swedish_ci": 136,
"ucs2_turkish_ci": 137,
"ucs2_czech_ci": 138,
"ucs2_danish_ci": 139,
"ucs2_lithuanian_ci": 140,
"ucs2_slovak_ci": 141,
"ucs2_spanish2_ci": 142,
"ucs2_roman_ci": 143,
"ucs2_persian_ci": 144,
"ucs2_esperanto_ci": 145,
"ucs2_hungarian_ci": 146,
"ucs2_sinhala_ci": 147,
"ucs2_german2_ci": 148,
"ucs2_croatian_ci": 149,
"ucs2_unicode_520_ci": 150,
"ucs2_vietnamese_ci": 151,
"ucs2_general_mysql500_ci": 159,
"utf32_unicode_ci": 160,
"utf32_icelandic_ci": 161,
"utf32_latvian_ci": 162,
"utf32_romanian_ci": 163,
"utf32_slovenian_ci": 164,
"utf32_polish_ci": 165,
"utf32_estonian_ci": 166,
"utf32_spanish_ci": 167,
"utf32_swedish_ci": 168,
"utf32_turkish_ci": 169,
"utf32_czech_ci": 170,
"utf32_danish_ci": 171,
"utf32_lithuanian_ci": 172,
"utf32_slovak_ci": 173,
"utf32_spanish2_ci": 174,
"utf32_roman_ci": 175,
"utf32_persian_ci": 176,
"utf32_esperanto_ci": 177,
"utf32_hungarian_ci": 178,
"utf32_sinhala_ci": 179,
"utf32_german2_ci": 180,
"utf32_croatian_ci": 181,
"utf32_unicode_520_ci": 182,
"utf32_vietnamese_ci": 183,
"utf8_unicode_ci": 192,
"utf8_icelandic_ci": 193,
"utf8_latvian_ci": 194,
"utf8_romanian_ci": 195,
"utf8_slovenian_ci": 196,
"utf8_polish_ci": 197,
"utf8_estonian_ci": 198,
"utf8_spanish_ci": 199,
"utf8_swedish_ci": 200,
"utf8_turkish_ci": 201,
"utf8_czech_ci": 202,
"utf8_danish_ci": 203,
"utf8_lithuanian_ci": 204,
"utf8_slovak_ci": 205,
"utf8_spanish2_ci": 206,
"utf8_roman_ci": 207,
"utf8_persian_ci": 208,
"utf8_esperanto_ci": 209,
"utf8_hungarian_ci": 210,
"utf8_sinhala_ci": 211,
"utf8_german2_ci": 212,
"utf8_croatian_ci": 213,
"utf8_unicode_520_ci": 214,
"utf8_vietnamese_ci": 215,
"utf8_general_mysql500_ci": 223,
"utf8mb4_unicode_ci": 224,
"utf8mb4_icelandic_ci": 225,
"utf8mb4_latvian_ci": 226,
"utf8mb4_romanian_ci": 227,
"utf8mb4_slovenian_ci": 228,
"utf8mb4_polish_ci": 229,
"utf8mb4_estonian_ci": 230,
"utf8mb4_spanish_ci": 231,
"utf8mb4_swedish_ci": 232,
"utf8mb4_turkish_ci": 233,
"utf8mb4_czech_ci": 234,
"utf8mb4_danish_ci": 235,
"utf8mb4_lithuanian_ci": 236,
"utf8mb4_slovak_ci": 237,
"utf8mb4_spanish2_ci": 238,
"utf8mb4_roman_ci": 239,
"utf8mb4_persian_ci": 240,
"utf8mb4_esperanto_ci": 241,
"utf8mb4_hungarian_ci": 242,
"utf8mb4_sinhala_ci": 243,
"utf8mb4_german2_ci": 244,
"utf8mb4_croatian_ci": 245,
"utf8mb4_unicode_520_ci": 246,
"utf8mb4_vietnamese_ci": 247,
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"crypto/tls"
"database/sql/driver"
"errors"
"net"
"strings"
"time"
)
type mysqlConn struct {
buf buffer
netConn net.Conn
affectedRows uint64
insertId uint64
cfg *config
maxPacketAllowed int
maxWriteSize int
flags clientFlag
sequence uint8
parseTime bool
strict bool
}
type config struct {
user string
passwd string
net string
addr string
dbname string
params map[string]string
loc *time.Location
tls *tls.Config
timeout time.Duration
collation uint8
allowAllFiles bool
allowOldPasswords bool
clientFoundRows bool
}
// Handles parameters set in DSN after the connection is established
func (mc *mysqlConn) handleParams() (err error) {
for param, val := range mc.cfg.params {
switch param {
// Charset
case "charset":
charsets := strings.Split(val, ",")
for i := range charsets {
// ignore errors here - a charset may not exist
err = mc.exec("SET NAMES " + charsets[i])
if err == nil {
break
}
}
if err != nil {
return
}
// time.Time parsing
case "parseTime":
var isBool bool
mc.parseTime, isBool = readBool(val)
if !isBool {
return errors.New("Invalid Bool value: " + val)
}
// Strict mode
case "strict":
var isBool bool
mc.strict, isBool = readBool(val)
if !isBool {
return errors.New("Invalid Bool value: " + val)
}
// Compression
case "compress":
err = errors.New("Compression not implemented yet")
return
// System Vars
default:
err = mc.exec("SET " + param + "=" + val + "")
if err != nil {
return
}
}
}
return
}
func (mc *mysqlConn) Begin() (driver.Tx, error) {
if mc.netConn == nil {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
err := mc.exec("START TRANSACTION")
if err == nil {
return &mysqlTx{mc}, err
}
return nil, err
}
func (mc *mysqlConn) Close() (err error) {
// Makes Close idempotent
if mc.netConn != nil {
err = mc.writeCommandPacket(comQuit)
if err == nil {
err = mc.netConn.Close()
} else {
mc.netConn.Close()
}
mc.netConn = nil
}
mc.cfg = nil
mc.buf.rd = nil
return
}
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
if mc.netConn == nil {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
// Send command
err := mc.writeCommandPacketStr(comStmtPrepare, query)
if err != nil {
return nil, err
}
stmt := &mysqlStmt{
mc: mc,
}
// Read Result
columnCount, err := stmt.readPrepareResultPacket()
if err == nil {
if stmt.paramCount > 0 {
if err = mc.readUntilEOF(); err != nil {
return nil, err
}
}
if columnCount > 0 {
err = mc.readUntilEOF()
}
}
return stmt, err
}
func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
if mc.netConn == nil {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
if len(args) == 0 { // no args, fastpath
mc.affectedRows = 0
mc.insertId = 0
err := mc.exec(query)
if err == nil {
return &mysqlResult{
affectedRows: int64(mc.affectedRows),
insertId: int64(mc.insertId),
}, err
}
return nil, err
}
// with args, must use prepared stmt
return nil, driver.ErrSkip
}
// Internal function to execute commands
func (mc *mysqlConn) exec(query string) error {
// Send command
err := mc.writeCommandPacketStr(comQuery, query)
if err != nil {
return err
}
// Read Result
resLen, err := mc.readResultSetHeaderPacket()
if err == nil && resLen > 0 {
if err = mc.readUntilEOF(); err != nil {
return err
}
err = mc.readUntilEOF()
}
return err
}
func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
if mc.netConn == nil {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
if len(args) == 0 { // no args, fastpath
// Send command
err := mc.writeCommandPacketStr(comQuery, query)
if err == nil {
// Read Result
var resLen int
resLen, err = mc.readResultSetHeaderPacket()
if err == nil {
rows := new(textRows)
rows.mc = mc
if resLen == 0 {
// no columns, no more data
return emptyRows{}, nil
}
// Columns
rows.columns, err = mc.readColumns(resLen)
return rows, err
}
}
return nil, err
}
// with args, must use prepared stmt
return nil, driver.ErrSkip
}
// Gets the value of the given MySQL System Variable
// The returned byte slice is only valid until the next read
func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
// Send command
if err := mc.writeCommandPacketStr(comQuery, "SELECT @@"+name); err != nil {
return nil, err
}
// Read Result
resLen, err := mc.readResultSetHeaderPacket()
if err == nil {
rows := new(textRows)
rows.mc = mc
if resLen > 0 {
// Columns
if err := mc.readUntilEOF(); err != nil {
return nil, err
}
}
dest := make([]driver.Value, resLen)
if err = rows.readRow(dest); err == nil {
return dest[0].([]byte), mc.readUntilEOF()
}
}
return nil, err
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
const (
minProtocolVersion byte = 10
maxPacketSize = 1<<24 - 1
timeFormat = "2006-01-02 15:04:05.999999"
)
// MySQL constants documentation:
// http://dev.mysql.com/doc/internals/en/client-server-protocol.html
const (
iOK byte = 0x00
iLocalInFile byte = 0xfb
iEOF byte = 0xfe
iERR byte = 0xff
)
type clientFlag uint32
const (
clientLongPassword clientFlag = 1 << iota
clientFoundRows
clientLongFlag
clientConnectWithDB
clientNoSchema
clientCompress
clientODBC
clientLocalFiles
clientIgnoreSpace
clientProtocol41
clientInteractive
clientSSL
clientIgnoreSIGPIPE
clientTransactions
clientReserved
clientSecureConn
clientMultiStatements
clientMultiResults
)
const (
comQuit byte = iota + 1
comInitDB
comQuery
comFieldList
comCreateDB
comDropDB
comRefresh
comShutdown
comStatistics
comProcessInfo
comConnect
comProcessKill
comDebug
comPing
comTime
comDelayedInsert
comChangeUser
comBinlogDump
comTableDump
comConnectOut
comRegiserSlave
comStmtPrepare
comStmtExecute
comStmtSendLongData
comStmtClose
comStmtReset
comSetOption
comStmtFetch
)
const (
fieldTypeDecimal byte = iota
fieldTypeTiny
fieldTypeShort
fieldTypeLong
fieldTypeFloat
fieldTypeDouble
fieldTypeNULL
fieldTypeTimestamp
fieldTypeLongLong
fieldTypeInt24
fieldTypeDate
fieldTypeTime
fieldTypeDateTime
fieldTypeYear
fieldTypeNewDate
fieldTypeVarChar
fieldTypeBit
)
const (
fieldTypeNewDecimal byte = iota + 0xf6
fieldTypeEnum
fieldTypeSet
fieldTypeTinyBLOB
fieldTypeMediumBLOB
fieldTypeLongBLOB
fieldTypeBLOB
fieldTypeVarString
fieldTypeString
fieldTypeGeometry
)
type fieldFlag uint16
const (
flagNotNULL fieldFlag = 1 << iota
flagPriKey
flagUniqueKey
flagMultipleKey
flagBLOB
flagUnsigned
flagZeroFill
flagBinary
flagEnum
flagAutoIncrement
flagTimestamp
flagSet
flagUnknown1
flagUnknown2
flagUnknown3
flagUnknown4
)
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// The driver should be used via the database/sql package:
//
// import "database/sql"
// import _ "github.com/go-sql-driver/mysql"
//
// db, err := sql.Open("mysql", "user:password@/dbname")
//
// See https://github.com/go-sql-driver/mysql#usage for details
package mysql
import (
"database/sql"
"database/sql/driver"
"net"
)
// This struct is exported to make the driver directly accessible.
// In general the driver is used via the database/sql package.
type MySQLDriver struct{}
// DialFunc is a function which can be used to establish the network connection.
// Custom dial functions must be registered with RegisterDial
type DialFunc func(addr string) (net.Conn, error)
var dials map[string]DialFunc
// RegisterDial registers a custom dial function. It can then be used by the
// network address mynet(addr), where mynet is the registered new network.
// addr is passed as a parameter to the dial function.
func RegisterDial(net string, dial DialFunc) {
if dials == nil {
dials = make(map[string]DialFunc)
}
dials[net] = dial
}
// Open new Connection.
// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
// the DSN string is formated
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
var err error
// New mysqlConn
mc := &mysqlConn{
maxPacketAllowed: maxPacketSize,
maxWriteSize: maxPacketSize - 1,
}
mc.cfg, err = parseDSN(dsn)
if err != nil {
return nil, err
}
// Connect to Server
if dial, ok := dials[mc.cfg.net]; ok {
mc.netConn, err = dial(mc.cfg.addr)
} else {
nd := net.Dialer{Timeout: mc.cfg.timeout}
mc.netConn, err = nd.Dial(mc.cfg.net, mc.cfg.addr)
}
if err != nil {
return nil, err
}
// Enable TCP Keepalives on TCP connections
if tc, ok := mc.netConn.(*net.TCPConn); ok {
if err := tc.SetKeepAlive(true); err != nil {
mc.Close()
return nil, err
}
}
mc.buf = newBuffer(mc.netConn)
// Reading Handshake Initialization Packet
cipher, err := mc.readInitPacket()
if err != nil {
mc.Close()
return nil, err
}
// Send Client Authentication Packet
if err = mc.writeAuthPacket(cipher); err != nil {
mc.Close()
return nil, err
}
// Read Result Packet
err = mc.readResultOK()
if err != nil {
// Retry with old authentication method, if allowed
if mc.cfg != nil && mc.cfg.allowOldPasswords && err == ErrOldPassword {
if err = mc.writeOldAuthPacket(cipher); err != nil {
mc.Close()
return nil, err
}
if err = mc.readResultOK(); err != nil {
mc.Close()
return nil, err
}
} else {
mc.Close()
return nil, err
}
}
// Get max allowed packet size
maxap, err := mc.getSystemVar("max_allowed_packet")
if err != nil {
mc.Close()
return nil, err
}
mc.maxPacketAllowed = stringToInt(maxap) - 1
if mc.maxPacketAllowed < maxPacketSize {
mc.maxWriteSize = mc.maxPacketAllowed
}
// Handle DSN Params
err = mc.handleParams()
if err != nil {
mc.Close()
return nil, err
}
return mc, nil
}
func init() {
sql.Register("mysql", &MySQLDriver{})
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"database/sql/driver"
"errors"
"fmt"
"io"
"log"
"os"
)
// Various errors the driver might return. Can change between driver versions.
var (
ErrInvalidConn = errors.New("Invalid Connection")
ErrMalformPkt = errors.New("Malformed Packet")
ErrNoTLS = errors.New("TLS encryption requested but server does not support TLS")
ErrOldPassword = errors.New("This server only supports the insecure old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords")
ErrOldProtocol = errors.New("MySQL-Server does not support required Protocol 41+")
ErrPktSync = errors.New("Commands out of sync. You can't run this command now")
ErrPktSyncMul = errors.New("Commands out of sync. Did you run multiple statements at once?")
ErrPktTooLarge = errors.New("Packet for query is too large. You can change this value on the server by adjusting the 'max_allowed_packet' variable.")
ErrBusyBuffer = errors.New("Busy buffer")
)
var errLog Logger = log.New(os.Stderr, "[MySQL] ", log.Ldate|log.Ltime|log.Lshortfile)
// Logger is used to log critical error messages.
type Logger interface {
Print(v ...interface{})
}
// SetLogger is used to set the logger for critical errors.
// The initial logger is os.Stderr.
func SetLogger(logger Logger) error {
if logger == nil {
return errors.New("logger is nil")
}
errLog = logger
return nil
}
// MySQLError is an error type which represents a single MySQL error
type MySQLError struct {
Number uint16
Message string
}
func (me *MySQLError) Error() string {
return fmt.Sprintf("Error %d: %s", me.Number, me.Message)
}
// MySQLWarnings is an error type which represents a group of one or more MySQL
// warnings
type MySQLWarnings []MySQLWarning
func (mws MySQLWarnings) Error() string {
var msg string
for i, warning := range mws {
if i > 0 {
msg += "\r\n"
}
msg += fmt.Sprintf(
"%s %s: %s",
warning.Level,
warning.Code,
warning.Message,
)
}
return msg
}
// MySQLWarning is an error type which represents a single MySQL warning.
// Warnings are returned in groups only. See MySQLWarnings
type MySQLWarning struct {
Level string
Code string
Message string
}
func (mc *mysqlConn) getWarnings() (err error) {
rows, err := mc.Query("SHOW WARNINGS", nil)
if err != nil {
return
}
var warnings = MySQLWarnings{}
var values = make([]driver.Value, 3)
for {
err = rows.Next(values)
switch err {
case nil:
warning := MySQLWarning{}
if raw, ok := values[0].([]byte); ok {
warning.Level = string(raw)
} else {
warning.Level = fmt.Sprintf("%s", values[0])
}
if raw, ok := values[1].([]byte); ok {
warning.Code = string(raw)
} else {
warning.Code = fmt.Sprintf("%s", values[1])
}
if raw, ok := values[2].([]byte); ok {
warning.Message = string(raw)
} else {
warning.Message = fmt.Sprintf("%s", values[0])
}
warnings = append(warnings, warning)
case io.EOF:
return warnings
default:
rows.Close()
return
}
}
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"bytes"
"log"
"testing"
)
func TestErrorsSetLogger(t *testing.T) {
previous := errLog
defer func() {
errLog = previous
}()
// set up logger
const expected = "prefix: test\n"
buffer := bytes.NewBuffer(make([]byte, 0, 64))
logger := log.New(buffer, "prefix: ", 0)
// print
SetLogger(logger)
errLog.Print("test")
// check result
if actual := buffer.String(); actual != expected {
t.Errorf("expected %q, got %q", expected, actual)
}
}
func TestErrorsStrictIgnoreNotes(t *testing.T) {
runTests(t, dsn+"&sql_notes=false", func(dbt *DBTest) {
dbt.mustExec("DROP TABLE IF EXISTS does_not_exist")
})
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"fmt"
"io"
"os"
"strings"
)
var (
fileRegister map[string]bool
readerRegister map[string]func() io.Reader
)
// RegisterLocalFile adds the given file to the file whitelist,
// so that it can be used by "LOAD DATA LOCAL INFILE <filepath>".
// Alternatively you can allow the use of all local files with
// the DSN parameter 'allowAllFiles=true'
//
// filePath := "/home/gopher/data.csv"
// mysql.RegisterLocalFile(filePath)
// err := db.Exec("LOAD DATA LOCAL INFILE '" + filePath + "' INTO TABLE foo")
// if err != nil {
// ...
//
func RegisterLocalFile(filePath string) {
// lazy map init
if fileRegister == nil {
fileRegister = make(map[string]bool)
}
fileRegister[strings.Trim(filePath, `"`)] = true
}
// DeregisterLocalFile removes the given filepath from the whitelist.
func DeregisterLocalFile(filePath string) {
delete(fileRegister, strings.Trim(filePath, `"`))
}
// RegisterReaderHandler registers a handler function which is used
// to receive a io.Reader.
// The Reader can be used by "LOAD DATA LOCAL INFILE Reader::<name>".
// If the handler returns a io.ReadCloser Close() is called when the
// request is finished.
//
// mysql.RegisterReaderHandler("data", func() io.Reader {
// var csvReader io.Reader // Some Reader that returns CSV data
// ... // Open Reader here
// return csvReader
// })
// err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE foo")
// if err != nil {
// ...
//
func RegisterReaderHandler(name string, handler func() io.Reader) {
// lazy map init
if readerRegister == nil {
readerRegister = make(map[string]func() io.Reader)
}
readerRegister[name] = handler
}
// DeregisterReaderHandler removes the ReaderHandler function with
// the given name from the registry.
func DeregisterReaderHandler(name string) {
delete(readerRegister, name)
}
func deferredClose(err *error, closer io.Closer) {
closeErr := closer.Close()
if *err == nil {
*err = closeErr
}
}
func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
var rdr io.Reader
var data []byte
if strings.HasPrefix(name, "Reader::") { // io.Reader
name = name[8:]
if handler, inMap := readerRegister[name]; inMap {
rdr = handler()
if rdr != nil {
data = make([]byte, 4+mc.maxWriteSize)
if cl, ok := rdr.(io.Closer); ok {
defer deferredClose(&err, cl)
}
} else {
err = fmt.Errorf("Reader '%s' is <nil>", name)
}
} else {
err = fmt.Errorf("Reader '%s' is not registered", name)
}
} else { // File
name = strings.Trim(name, `"`)
if mc.cfg.allowAllFiles || fileRegister[name] {
var file *os.File
var fi os.FileInfo
if file, err = os.Open(name); err == nil {
defer deferredClose(&err, file)
// get file size
if fi, err = file.Stat(); err == nil {
rdr = file
if fileSize := int(fi.Size()); fileSize <= mc.maxWriteSize {
data = make([]byte, 4+fileSize)
} else if fileSize <= mc.maxPacketAllowed {
data = make([]byte, 4+mc.maxWriteSize)
} else {
err = fmt.Errorf("Local File '%s' too large: Size: %d, Max: %d", name, fileSize, mc.maxPacketAllowed)
}
}
}
} else {
err = fmt.Errorf("Local File '%s' is not registered. Use the DSN parameter 'allowAllFiles=true' to allow all files", name)
}
}
// send content packets
if err == nil {
var n int
for err == nil {
n, err = rdr.Read(data[4:])
if n > 0 {
if ioErr := mc.writePacket(data[:4+n]); ioErr != nil {
return ioErr
}
}
}
if err == io.EOF {
err = nil
}
}
// send empty packet (termination)
if data == nil {
data = make([]byte, 4)
}
if ioErr := mc.writePacket(data[:4]); ioErr != nil {
return ioErr
}
// read OK packet
if err == nil {
return mc.readResultOK()
} else {
mc.readPacket()
}
return err
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
type mysqlResult struct {
affectedRows int64
insertId int64
}
func (res *mysqlResult) LastInsertId() (int64, error) {
return res.insertId, nil
}
func (res *mysqlResult) RowsAffected() (int64, error) {
return res.affectedRows, nil
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"database/sql/driver"
"io"
)
type mysqlField struct {
name string
flags fieldFlag
fieldType byte
decimals byte
}
type mysqlRows struct {
mc *mysqlConn
columns []mysqlField
}
type binaryRows struct {
mysqlRows
}
type textRows struct {
mysqlRows
}
type emptyRows struct{}
func (rows *mysqlRows) Columns() []string {
columns := make([]string, len(rows.columns))
for i := range columns {
columns[i] = rows.columns[i].name
}
return columns
}
func (rows *mysqlRows) Close() error {
mc := rows.mc
if mc == nil {
return nil
}
if mc.netConn == nil {
return ErrInvalidConn
}
// Remove unread packets from stream
err := mc.readUntilEOF()
rows.mc = nil
return err
}
func (rows *binaryRows) Next(dest []driver.Value) error {
if mc := rows.mc; mc != nil {
if mc.netConn == nil {
return ErrInvalidConn
}
// Fetch next row from stream
if err := rows.readRow(dest); err != io.EOF {
return err
}
rows.mc = nil
}
return io.EOF
}
func (rows *textRows) Next(dest []driver.Value) error {
if mc := rows.mc; mc != nil {
if mc.netConn == nil {
return ErrInvalidConn
}
// Fetch next row from stream
if err := rows.readRow(dest); err != io.EOF {
return err
}
rows.mc = nil
}
return io.EOF
}
func (rows emptyRows) Columns() []string {
return nil
}
func (rows emptyRows) Close() error {
return nil
}
func (rows emptyRows) Next(dest []driver.Value) error {
return io.EOF
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"database/sql/driver"
)
type mysqlStmt struct {
mc *mysqlConn
id uint32
paramCount int
columns []mysqlField // cached from the first query
}
func (stmt *mysqlStmt) Close() error {
if stmt.mc == nil || stmt.mc.netConn == nil {
errLog.Print(ErrInvalidConn)
return driver.ErrBadConn
}
err := stmt.mc.writeCommandPacketUint32(comStmtClose, stmt.id)
stmt.mc = nil
return err
}
func (stmt *mysqlStmt) NumInput() int {
return stmt.paramCount
}
func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
if stmt.mc.netConn == nil {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
// Send command
err := stmt.writeExecutePacket(args)
if err != nil {
return nil, err
}
mc := stmt.mc
mc.affectedRows = 0
mc.insertId = 0
// Read Result
resLen, err := mc.readResultSetHeaderPacket()
if err == nil {
if resLen > 0 {
// Columns
err = mc.readUntilEOF()
if err != nil {
return nil, err
}
// Rows
err = mc.readUntilEOF()
}
if err == nil {
return &mysqlResult{
affectedRows: int64(mc.affectedRows),
insertId: int64(mc.insertId),
}, nil
}
}
return nil, err
}
func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
if stmt.mc.netConn == nil {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
// Send command
err := stmt.writeExecutePacket(args)
if err != nil {
return nil, err
}
mc := stmt.mc
// Read Result
resLen, err := mc.readResultSetHeaderPacket()
if err != nil {
return nil, err
}
rows := new(binaryRows)
rows.mc = mc
if resLen > 0 {
// Columns
// If not cached, read them and cache them
if stmt.columns == nil {
rows.columns, err = mc.readColumns(resLen)
stmt.columns = rows.columns
} else {
rows.columns = stmt.columns
err = mc.readUntilEOF()
}
}
return rows, err
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
type mysqlTx struct {
mc *mysqlConn
}
func (tx *mysqlTx) Commit() (err error) {
if tx.mc == nil || tx.mc.netConn == nil {
return ErrInvalidConn
}
err = tx.mc.exec("COMMIT")
tx.mc = nil
return
}
func (tx *mysqlTx) Rollback() (err error) {
if tx.mc == nil || tx.mc.netConn == nil {
return ErrInvalidConn
}
err = tx.mc.exec("ROLLBACK")
tx.mc = nil
return
}
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"bytes"
"encoding/binary"
"fmt"
"testing"
"time"
)
var testDSNs = []struct {
in string
out string
loc *time.Location
}{
{"username:password@protocol(address)/dbname?param=value", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
{"user@unix(/path/to/socket)/dbname?charset=utf8", "&{user:user passwd: net:unix addr:/path/to/socket dbname:dbname params:map[charset:utf8] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
{"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true", "&{user:user passwd:password net:tcp addr:localhost:5555 dbname:dbname params:map[charset:utf8] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
{"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify", "&{user:user passwd:password net:tcp addr:localhost:5555 dbname:dbname params:map[charset:utf8mb4,utf8] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
{"user:password@/dbname?loc=UTC&timeout=30s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci", "&{user:user passwd:password net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p tls:<nil> timeout:30000000000 collation:224 allowAllFiles:true allowOldPasswords:true clientFoundRows:true}", time.UTC},
{"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local", "&{user:user passwd:p@ss(word) net:tcp addr:[de:ad:be:ef::ca:fe]:80 dbname:dbname params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.Local},
{"/dbname", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
{"@/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
{"/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
{"", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
{"user:p@/ssword@/", "&{user:user passwd:p@/ssword net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
{"unix/?arg=%2Fsome%2Fpath.ext", "&{user: passwd: net:unix addr:/tmp/mysql.sock dbname: params:map[arg:/some/path.ext] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
}
func TestDSNParser(t *testing.T) {
var cfg *config
var err error
var res string
for i, tst := range testDSNs {
cfg, err = parseDSN(tst.in)
if err != nil {
t.Error(err.Error())
}
// pointer not static
cfg.tls = nil
res = fmt.Sprintf("%+v", cfg)
if res != fmt.Sprintf(tst.out, tst.loc) {
t.Errorf("%d. parseDSN(%q) => %q, want %q", i, tst.in, res, fmt.Sprintf(tst.out, tst.loc))
}
}
}
func TestDSNParserInvalid(t *testing.T) {
var invalidDSNs = []string{
"@net(addr/", // no closing brace
"@tcp(/", // no closing brace
"tcp(/", // no closing brace
"(/", // no closing brace
"net(addr)//", // unescaped
"user:pass@tcp(1.2.3.4:3306)", // no trailing slash
//"/dbname?arg=/some/unescaped/path",
}
for i, tst := range invalidDSNs {
if _, err := parseDSN(tst); err == nil {
t.Errorf("invalid DSN #%d. (%s) didn't error!", i, tst)
}
}
}
func BenchmarkParseDSN(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for _, tst := range testDSNs {
if _, err := parseDSN(tst.in); err != nil {
b.Error(err.Error())
}
}
}
}
func TestScanNullTime(t *testing.T) {
var scanTests = []struct {
in interface{}
error bool
valid bool
time time.Time
}{
{tDate, false, true, tDate},
{sDate, false, true, tDate},
{[]byte(sDate), false, true, tDate},
{tDateTime, false, true, tDateTime},
{sDateTime, false, true, tDateTime},
{[]byte(sDateTime), false, true, tDateTime},
{tDate0, false, true, tDate0},
{sDate0, false, true, tDate0},
{[]byte(sDate0), false, true, tDate0},
{sDateTime0, false, true, tDate0},
{[]byte(sDateTime0), false, true, tDate0},
{"", true, false, tDate0},
{"1234", true, false, tDate0},
{0, true, false, tDate0},
}
var nt = NullTime{}
var err error
for _, tst := range scanTests {
err = nt.Scan(tst.in)
if (err != nil) != tst.error {
t.Errorf("%v: expected error status %t, got %t", tst.in, tst.error, (err != nil))
}
if nt.Valid != tst.valid {
t.Errorf("%v: expected valid status %t, got %t", tst.in, tst.valid, nt.Valid)
}
if nt.Time != tst.time {
t.Errorf("%v: expected time %v, got %v", tst.in, tst.time, nt.Time)
}
}
}
func TestLengthEncodedInteger(t *testing.T) {
var integerTests = []struct {
num uint64
encoded []byte
}{
{0x0000000000000000, []byte{0x00}},
{0x0000000000000012, []byte{0x12}},
{0x00000000000000fa, []byte{0xfa}},
{0x0000000000000100, []byte{0xfc, 0x00, 0x01}},
{0x0000000000001234, []byte{0xfc, 0x34, 0x12}},
{0x000000000000ffff, []byte{0xfc, 0xff, 0xff}},
{0x0000000000010000, []byte{0xfd, 0x00, 0x00, 0x01}},
{0x0000000000123456, []byte{0xfd, 0x56, 0x34, 0x12}},
{0x0000000000ffffff, []byte{0xfd, 0xff, 0xff, 0xff}},
{0x0000000001000000, []byte{0xfe, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}},
{0x123456789abcdef0, []byte{0xfe, 0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12}},
{0xffffffffffffffff, []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}},
}
for _, tst := range integerTests {
num, isNull, numLen := readLengthEncodedInteger(tst.encoded)
if isNull {
t.Errorf("%x: expected %d, got NULL", tst.encoded, tst.num)
}
if num != tst.num {
t.Errorf("%x: expected %d, got %d", tst.encoded, tst.num, num)
}
if numLen != len(tst.encoded) {
t.Errorf("%x: expected size %d, got %d", tst.encoded, len(tst.encoded), numLen)
}
encoded := appendLengthEncodedInteger(nil, num)
if !bytes.Equal(encoded, tst.encoded) {
t.Errorf("%v: expected %x, got %x", num, tst.encoded, encoded)
}
}
}
func TestOldPass(t *testing.T) {
scramble := []byte{9, 8, 7, 6, 5, 4, 3, 2}
vectors := []struct {
pass string
out string
}{
{" pass", "47575c5a435b4251"},
{"pass ", "47575c5a435b4251"},
{"123\t456", "575c47505b5b5559"},
{"C0mpl!ca ted#PASS123", "5d5d554849584a45"},
}
for _, tuple := range vectors {
ours := scrambleOldPassword(scramble, []byte(tuple.pass))
if tuple.out != fmt.Sprintf("%x", ours) {
t.Errorf("Failed old password %q", tuple.pass)
}
}
}
func TestFormatBinaryDateTime(t *testing.T) {
rawDate := [11]byte{}
binary.LittleEndian.PutUint16(rawDate[:2], 1978) // years
rawDate[2] = 12 // months
rawDate[3] = 30 // days
rawDate[4] = 15 // hours
rawDate[5] = 46 // minutes
rawDate[6] = 23 // seconds
binary.LittleEndian.PutUint32(rawDate[7:], 987654) // microseconds
expect := func(expected string, inlen, outlen uint8) {
actual, _ := formatBinaryDateTime(rawDate[:inlen], outlen, false)
bytes, ok := actual.([]byte)
if !ok {
t.Errorf("formatBinaryDateTime must return []byte, was %T", actual)
}
if string(bytes) != expected {
t.Errorf(
"expected %q, got %q for length in %d, out %d",
bytes, actual, inlen, outlen,
)
}
}
expect("0000-00-00", 0, 10)
expect("0000-00-00 00:00:00", 0, 19)
expect("1978-12-30", 4, 10)
expect("1978-12-30 15:46:23", 7, 19)
expect("1978-12-30 15:46:23.987654", 11, 26)
}
package convey
import "github.com/smartystreets/goconvey/convey/assertions"
var (
ShouldEqual = assertions.ShouldEqual
ShouldNotEqual = assertions.ShouldNotEqual
ShouldAlmostEqual = assertions.ShouldAlmostEqual
ShouldNotAlmostEqual = assertions.ShouldNotAlmostEqual
ShouldResemble = assertions.ShouldResemble
ShouldNotResemble = assertions.ShouldNotResemble
ShouldPointTo = assertions.ShouldPointTo
ShouldNotPointTo = assertions.ShouldNotPointTo
ShouldBeNil = assertions.ShouldBeNil
ShouldNotBeNil = assertions.ShouldNotBeNil
ShouldBeTrue = assertions.ShouldBeTrue
ShouldBeFalse = assertions.ShouldBeFalse
ShouldBeZeroValue = assertions.ShouldBeZeroValue
ShouldBeGreaterThan = assertions.ShouldBeGreaterThan
ShouldBeGreaterThanOrEqualTo = assertions.ShouldBeGreaterThanOrEqualTo
ShouldBeLessThan = assertions.ShouldBeLessThan
ShouldBeLessThanOrEqualTo = assertions.ShouldBeLessThanOrEqualTo
ShouldBeBetween = assertions.ShouldBeBetween
ShouldNotBeBetween = assertions.ShouldNotBeBetween
ShouldContain = assertions.ShouldContain
ShouldNotContain = assertions.ShouldNotContain
ShouldBeIn = assertions.ShouldBeIn
ShouldNotBeIn = assertions.ShouldNotBeIn
ShouldBeEmpty = assertions.ShouldBeEmpty
ShouldNotBeEmpty = assertions.ShouldNotBeEmpty
ShouldStartWith = assertions.ShouldStartWith
ShouldNotStartWith = assertions.ShouldNotStartWith
ShouldEndWith = assertions.ShouldEndWith
ShouldNotEndWith = assertions.ShouldNotEndWith
ShouldBeBlank = assertions.ShouldBeBlank
ShouldNotBeBlank = assertions.ShouldNotBeBlank
ShouldContainSubstring = assertions.ShouldContainSubstring
ShouldNotContainSubstring = assertions.ShouldNotContainSubstring
ShouldPanic = assertions.ShouldPanic
ShouldNotPanic = assertions.ShouldNotPanic
ShouldPanicWith = assertions.ShouldPanicWith
ShouldNotPanicWith = assertions.ShouldNotPanicWith
ShouldHaveSameTypeAs = assertions.ShouldHaveSameTypeAs
ShouldNotHaveSameTypeAs = assertions.ShouldNotHaveSameTypeAs
ShouldImplement = assertions.ShouldImplement
ShouldNotImplement = assertions.ShouldNotImplement
ShouldHappenBefore = assertions.ShouldHappenBefore
ShouldHappenOnOrBefore = assertions.ShouldHappenOnOrBefore
ShouldHappenAfter = assertions.ShouldHappenAfter
ShouldHappenOnOrAfter = assertions.ShouldHappenOnOrAfter
ShouldHappenBetween = assertions.ShouldHappenBetween
ShouldHappenOnOrBetween = assertions.ShouldHappenOnOrBetween
ShouldNotHappenOnOrBetween = assertions.ShouldNotHappenOnOrBetween
ShouldHappenWithin = assertions.ShouldHappenWithin
ShouldNotHappenWithin = assertions.ShouldNotHappenWithin
ShouldBeChronological = assertions.ShouldBeChronological
)
package assertions
import (
"fmt"
"reflect"
"github.com/jacobsa/oglematchers"
)
// ShouldContain receives exactly two parameters. The first is a slice and the
// second is a proposed member. Membership is determined using ShouldEqual.
func ShouldContain(actual interface{}, expected ...interface{}) string {
if fail := need(1, expected); fail != success {
return fail
}
if matchError := oglematchers.Contains(expected[0]).Matches(actual); matchError != nil {
typeName := reflect.TypeOf(actual)
if fmt.Sprintf("%v", matchError) == "which is not a slice or array" {
return fmt.Sprintf(shouldHaveBeenAValidCollection, typeName)
}
return fmt.Sprintf(shouldHaveContained, typeName, expected[0])
}
return success
}
// ShouldNotContain receives exactly two parameters. The first is a slice and the
// second is a proposed member. Membership is determinied using ShouldEqual.
func ShouldNotContain(actual interface{}, expected ...interface{}) string {
if fail := need(1, expected); fail != success {
return fail
}
typeName := reflect.TypeOf(actual)
if matchError := oglematchers.Contains(expected[0]).Matches(actual); matchError != nil {
if fmt.Sprintf("%v", matchError) == "which is not a slice or array" {
return fmt.Sprintf(shouldHaveBeenAValidCollection, typeName)
}
return success
}
return fmt.Sprintf(shouldNotHaveContained, typeName, expected[0])
}
// ShouldBeIn receives at least 2 parameters. The first is a proposed member of the collection
// that is passed in either as the second parameter, or of the collection that is comprised
// of all the remaining parameters. This assertion ensures that the proposed member is in
// the collection (using ShouldEqual).
func ShouldBeIn(actual interface{}, expected ...interface{}) string {
if fail := atLeast(1, expected); fail != success {
return fail
}
if len(expected) == 1 {
return shouldBeIn(actual, expected[0])
}
return shouldBeIn(actual, expected)
}
func shouldBeIn(actual interface{}, expected interface{}) string {
if matchError := oglematchers.Contains(actual).Matches(expected); matchError != nil {
return fmt.Sprintf(shouldHaveBeenIn, actual, reflect.TypeOf(expected))
}
return success
}
// ShouldNotBeIn receives at least 2 parameters. The first is a proposed member of the collection
// that is passed in either as the second parameter, or of the collection that is comprised
// of all the remaining parameters. This assertion ensures that the proposed member is NOT in
// the collection (using ShouldEqual).
func ShouldNotBeIn(actual interface{}, expected ...interface{}) string {
if fail := atLeast(1, expected); fail != success {
return fail
}
if len(expected) == 1 {
return shouldNotBeIn(actual, expected[0])
}
return shouldNotBeIn(actual, expected)
}
func shouldNotBeIn(actual interface{}, expected interface{}) string {
if matchError := oglematchers.Contains(actual).Matches(expected); matchError == nil {
return fmt.Sprintf(shouldNotHaveBeenIn, actual, reflect.TypeOf(expected))
}
return success
}
// ShouldBeEmpty receives a single parameter (actual) and determines whether or not
// calling len(actual) would return `0`. It obeys the rules specified by the len
// function for determining length: http://golang.org/pkg/builtin/#len
func ShouldBeEmpty(actual interface{}, expected ...interface{}) string {
if fail := need(0, expected); fail != success {
return fail
}
if actual == nil {
return success
}
value := reflect.ValueOf(actual)
switch value.Kind() {
case reflect.Slice:
if value.Len() == 0 {
return success
}
case reflect.Chan:
if value.Len() == 0 {
return success
}
case reflect.Map:
if value.Len() == 0 {
return success
}
case reflect.String:
if value.Len() == 0 {
return success
}
case reflect.Ptr:
elem := value.Elem()
kind := elem.Kind()
if (kind == reflect.Slice || kind == reflect.Array) && elem.Len() == 0 {
return success
}
}
return fmt.Sprintf(shouldHaveBeenEmpty, actual)
}
// ShouldNotBeEmpty receives a single parameter (actual) and determines whether or not
// calling len(actual) would return a value greater than zero. It obeys the rules
// specified by the `len` function for determining length: http://golang.org/pkg/builtin/#len
func ShouldNotBeEmpty(actual interface{}, expected ...interface{}) string {
if fail := need(0, expected); fail != success {
return fail
}
if empty := ShouldBeEmpty(actual, expected...); empty != success {
return success
}
return fmt.Sprintf(shouldNotHaveBeenEmpty, actual)
}
package assertions
import (
"fmt"
"testing"
"time"
)
func TestShouldContain(t *testing.T) {
fail(t, so([]int{}, ShouldContain), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so([]int{}, ShouldContain, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).")
fail(t, so(Thing1{}, ShouldContain, 1), "You must provide a valid container (was assertions.Thing1)!")
fail(t, so(nil, ShouldContain, 1), "You must provide a valid container (was <nil>)!")
fail(t, so([]int{1}, ShouldContain, 2), "Expected the container ([]int) to contain: '2' (but it didn't)!")
pass(t, so([]int{1}, ShouldContain, 1))
pass(t, so([]int{1, 2, 3}, ShouldContain, 2))
}
func TestShouldNotContain(t *testing.T) {
fail(t, so([]int{}, ShouldNotContain), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so([]int{}, ShouldNotContain, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).")
fail(t, so(Thing1{}, ShouldNotContain, 1), "You must provide a valid container (was assertions.Thing1)!")
fail(t, so(nil, ShouldNotContain, 1), "You must provide a valid container (was <nil>)!")
fail(t, so([]int{1}, ShouldNotContain, 1), "Expected the container ([]int) NOT to contain: '1' (but it did)!")
fail(t, so([]int{1, 2, 3}, ShouldNotContain, 2), "Expected the container ([]int) NOT to contain: '2' (but it did)!")
pass(t, so([]int{1}, ShouldNotContain, 2))
}
func TestShouldBeIn(t *testing.T) {
fail(t, so(4, ShouldBeIn), shouldHaveProvidedCollectionMembers)
container := []int{1, 2, 3, 4}
pass(t, so(4, ShouldBeIn, container))
pass(t, so(4, ShouldBeIn, 1, 2, 3, 4))
fail(t, so(4, ShouldBeIn, 1, 2, 3), "Expected '4' to be in the container ([]interface {}, but it wasn't)!")
fail(t, so(4, ShouldBeIn, []int{1, 2, 3}), "Expected '4' to be in the container ([]int, but it wasn't)!")
}
func TestShouldNotBeIn(t *testing.T) {
fail(t, so(4, ShouldNotBeIn), shouldHaveProvidedCollectionMembers)
container := []int{1, 2, 3, 4}
pass(t, so(42, ShouldNotBeIn, container))
pass(t, so(42, ShouldNotBeIn, 1, 2, 3, 4))
fail(t, so(2, ShouldNotBeIn, 1, 2, 3), "Expected '2' NOT to be in the container ([]interface {}, but it was)!")
fail(t, so(2, ShouldNotBeIn, []int{1, 2, 3}), "Expected '2' NOT to be in the container ([]int, but it was)!")
}
func TestShouldBeEmpty(t *testing.T) {
fail(t, so(1, ShouldBeEmpty, 2, 3), "This assertion requires exactly 0 comparison values (you provided 2).")
pass(t, so([]int{}, ShouldBeEmpty)) // empty slice
pass(t, so([]interface{}{}, ShouldBeEmpty)) // empty slice
pass(t, so(map[string]int{}, ShouldBeEmpty)) // empty map
pass(t, so("", ShouldBeEmpty)) // empty string
pass(t, so(&[]int{}, ShouldBeEmpty)) // pointer to empty slice
pass(t, so(&[0]int{}, ShouldBeEmpty)) // pointer to empty array
pass(t, so(nil, ShouldBeEmpty)) // nil
pass(t, so(make(chan string), ShouldBeEmpty)) // empty channel
fail(t, so([]int{1}, ShouldBeEmpty), "Expected [1] to be empty (but it wasn't)!") // non-empty slice
fail(t, so([]interface{}{1}, ShouldBeEmpty), "Expected [1] to be empty (but it wasn't)!") // non-empty slice
fail(t, so(map[string]int{"hi": 0}, ShouldBeEmpty), "Expected map[hi:0] to be empty (but it wasn't)!") // non-empty map
fail(t, so("hi", ShouldBeEmpty), "Expected hi to be empty (but it wasn't)!") // non-empty string
fail(t, so(&[]int{1}, ShouldBeEmpty), "Expected &[1] to be empty (but it wasn't)!") // pointer to non-empty slice
fail(t, so(&[1]int{1}, ShouldBeEmpty), "Expected &[1] to be empty (but it wasn't)!") // pointer to non-empty array
c := make(chan int, 1) // non-empty channel
go func() { c <- 1 }()
time.Sleep(time.Millisecond)
fail(t, so(c, ShouldBeEmpty), fmt.Sprintf("Expected %+v to be empty (but it wasn't)!", c))
}
func TestShouldNotBeEmpty(t *testing.T) {
fail(t, so(1, ShouldNotBeEmpty, 2, 3), "This assertion requires exactly 0 comparison values (you provided 2).")
fail(t, so([]int{}, ShouldNotBeEmpty), "Expected [] to NOT be empty (but it was)!") // empty slice
fail(t, so([]interface{}{}, ShouldNotBeEmpty), "Expected [] to NOT be empty (but it was)!") // empty slice
fail(t, so(map[string]int{}, ShouldNotBeEmpty), "Expected map[] to NOT be empty (but it was)!") // empty map
fail(t, so("", ShouldNotBeEmpty), "Expected to NOT be empty (but it was)!") // empty string
fail(t, so(&[]int{}, ShouldNotBeEmpty), "Expected &[] to NOT be empty (but it was)!") // pointer to empty slice
fail(t, so(&[0]int{}, ShouldNotBeEmpty), "Expected &[] to NOT be empty (but it was)!") // pointer to empty array
fail(t, so(nil, ShouldNotBeEmpty), "Expected <nil> to NOT be empty (but it was)!") // nil
c := make(chan int, 0) // non-empty channel
fail(t, so(c, ShouldNotBeEmpty), fmt.Sprintf("Expected %+v to NOT be empty (but it was)!", c)) // empty channel
pass(t, so([]int{1}, ShouldNotBeEmpty)) // non-empty slice
pass(t, so([]interface{}{1}, ShouldNotBeEmpty)) // non-empty slice
pass(t, so(map[string]int{"hi": 0}, ShouldNotBeEmpty)) // non-empty map
pass(t, so("hi", ShouldNotBeEmpty)) // non-empty string
pass(t, so(&[]int{1}, ShouldNotBeEmpty)) // pointer to non-empty slice
pass(t, so(&[1]int{1}, ShouldNotBeEmpty)) // pointer to non-empty array
c = make(chan int, 1)
go func() { c <- 1 }()
time.Sleep(time.Millisecond)
pass(t, so(c, ShouldNotBeEmpty))
}
// Package assertions contains the implementations for all assertions which
// are referenced in the convey package for use with the So(...) method.
package assertions
package assertions
import (
"errors"
"fmt"
"math"
"reflect"
"strings"
"github.com/jacobsa/oglematchers"
)
// default acceptable delta for ShouldAlmostEqual
const defaultDelta = 0.0000000001
// ShouldEqual receives exactly two parameters and does an equality check.
func ShouldEqual(actual interface{}, expected ...interface{}) string {
if message := need(1, expected); message != success {
return message
}
return shouldEqual(actual, expected[0])
}
func shouldEqual(actual, expected interface{}) (message string) {
defer func() {
if r := recover(); r != nil {
message = serializer.serialize(expected, actual, fmt.Sprintf(shouldHaveBeenEqual, expected, actual))
return
}
}()
if matchError := oglematchers.Equals(expected).Matches(actual); matchError != nil {
message = serializer.serialize(expected, actual, fmt.Sprintf(shouldHaveBeenEqual, expected, actual))
return
}
return success
}
// ShouldNotEqual receives exactly two parameters and does an inequality check.
func ShouldNotEqual(actual interface{}, expected ...interface{}) string {
if fail := need(1, expected); fail != success {
return fail
} else if ShouldEqual(actual, expected[0]) == success {
return fmt.Sprintf(shouldNotHaveBeenEqual, actual, expected[0])
}
return success
}
// ShouldAlmostEqual makes sure that two parameters are close enough to being equal.
// The acceptable delta may be specified with a third argument,
// or a very small default delta will be used.
func ShouldAlmostEqual(actual interface{}, expected ...interface{}) string {
actualFloat, expectedFloat, deltaFloat, err := cleanAlmostEqualInput(actual, expected...)
if err != "" {
return err
}
if math.Abs(actualFloat-expectedFloat) <= deltaFloat {
return success
} else {
return fmt.Sprintf(shouldHaveBeenAlmostEqual, actualFloat, expectedFloat)
}
}
// ShouldNotAlmostEqual is the inverse of ShouldAlmostEqual
func ShouldNotAlmostEqual(actual interface{}, expected ...interface{}) string {
actualFloat, expectedFloat, deltaFloat, err := cleanAlmostEqualInput(actual, expected...)
if err != "" {
return err
}
if math.Abs(actualFloat-expectedFloat) > deltaFloat {
return success
} else {
return fmt.Sprintf(shouldHaveNotBeenAlmostEqual, actualFloat, expectedFloat)
}
}
func cleanAlmostEqualInput(actual interface{}, expected ...interface{}) (float64, float64, float64, string) {
deltaFloat := 0.0000000001
if len(expected) == 0 {
return 0.0, 0.0, 0.0, "This assertion requires exactly one comparison value and an optional delta (you provided neither)"
} else if len(expected) == 2 {
delta, err := getFloat(expected[1])
if err != nil {
return 0.0, 0.0, 0.0, "delta must be a numerical type"
}
deltaFloat = delta
} else if len(expected) > 2 {
return 0.0, 0.0, 0.0, "This assertion requires exactly one comparison value and an optional delta (you provided more values)"
}
actualFloat, err := getFloat(actual)
if err != nil {
return 0.0, 0.0, 0.0, err.Error()
}
expectedFloat, err := getFloat(expected[0])
if err != nil {
return 0.0, 0.0, 0.0, err.Error()
}
return actualFloat, expectedFloat, deltaFloat, ""
}
// returns the float value of any real number, or error if it is not a numerical type
func getFloat(num interface{}) (float64, error) {
numValue := reflect.ValueOf(num)
numKind := numValue.Kind()
if numKind == reflect.Int ||
numKind == reflect.Int8 ||
numKind == reflect.Int16 ||
numKind == reflect.Int32 ||
numKind == reflect.Int64 {
return float64(numValue.Int()), nil
} else if numKind == reflect.Uint ||
numKind == reflect.Uint8 ||
numKind == reflect.Uint16 ||
numKind == reflect.Uint32 ||
numKind == reflect.Uint64 {
return float64(numValue.Uint()), nil
} else if numKind == reflect.Float32 ||
numKind == reflect.Float64 {
return numValue.Float(), nil
} else {
return 0.0, errors.New("must be a numerical type, but was " + numKind.String())
}
}
// ShouldResemble receives exactly two parameters and does a deep equal check (see reflect.DeepEqual)
func ShouldResemble(actual interface{}, expected ...interface{}) string {
if message := need(1, expected); message != success {
return message
}
if matchError := oglematchers.DeepEquals(expected[0]).Matches(actual); matchError != nil {
message := fmt.Sprintf(
shouldHaveResembled,
expected[0], expected[0],
actual, actual,
)
return serializer.serialize(
expected[0], actual, message)
}
return success
}
// ShouldNotResemble receives exactly two parameters and does an inverse deep equal check (see reflect.DeepEqual)
func ShouldNotResemble(actual interface{}, expected ...interface{}) string {
if message := need(1, expected); message != success {
return message
} else if ShouldResemble(actual, expected[0]) == success {
return fmt.Sprintf(
shouldNotHaveResembled,
actual, actual,
expected[0], expected[0],
)
}
return success
}
// ShouldPointTo receives exactly two parameters and checks to see that they point to the same address.
func ShouldPointTo(actual interface{}, expected ...interface{}) string {
if message := need(1, expected); message != success {
return message
}
return shouldPointTo(actual, expected[0])
}
func shouldPointTo(actual, expected interface{}) string {
actualValue := reflect.ValueOf(actual)
expectedValue := reflect.ValueOf(expected)
if ShouldNotBeNil(actual) != success {
return fmt.Sprintf(shouldHaveBeenNonNilPointer, "first", "nil")
} else if ShouldNotBeNil(expected) != success {
return fmt.Sprintf(shouldHaveBeenNonNilPointer, "second", "nil")
} else if actualValue.Kind() != reflect.Ptr {
return fmt.Sprintf(shouldHaveBeenNonNilPointer, "first", "not")
} else if expectedValue.Kind() != reflect.Ptr {
return fmt.Sprintf(shouldHaveBeenNonNilPointer, "second", "not")
} else if ShouldEqual(actualValue.Pointer(), expectedValue.Pointer()) != success {
actualAddress := reflect.ValueOf(actual).Pointer()
expectedAddress := reflect.ValueOf(expected).Pointer()
return serializer.serialize(expectedAddress, actualAddress, fmt.Sprintf(shouldHavePointedTo,
actual, actualAddress,
expected, expectedAddress))
}
return success
}
// ShouldNotPointTo receives exactly two parameters and checks to see that they point to different addresess.
func ShouldNotPointTo(actual interface{}, expected ...interface{}) string {
if message := need(1, expected); message != success {
return message
}
compare := ShouldPointTo(actual, expected[0])
if strings.HasPrefix(compare, shouldBePointers) {
return compare
} else if compare == success {
return fmt.Sprintf(shouldNotHavePointedTo, actual, expected[0], reflect.ValueOf(actual).Pointer())
}
return success
}
// ShouldBeNil receives a single parameter and ensures that it is nil.
func ShouldBeNil(actual interface{}, expected ...interface{}) string {
if fail := need(0, expected); fail != success {
return fail
} else if actual == nil {
return success
} else if interfaceHasNilValue(actual) {
return success
}
return fmt.Sprintf(shouldHaveBeenNil, actual)
}
func interfaceHasNilValue(actual interface{}) bool {
value := reflect.ValueOf(actual)
kind := value.Kind()
nilable := kind == reflect.Slice ||
kind == reflect.Chan ||
kind == reflect.Func ||
kind == reflect.Ptr ||
kind == reflect.Map
// Careful: reflect.Value.IsNil() will panic unless it's an interface, chan, map, func, slice, or ptr
// Reference: http://golang.org/pkg/reflect/#Value.IsNil
return nilable && value.IsNil()
}
// ShouldNotBeNil receives a single parameter and ensures that it is not nil.
func ShouldNotBeNil(actual interface{}, expected ...interface{}) string {
if fail := need(0, expected); fail != success {
return fail
} else if ShouldBeNil(actual) == success {
return fmt.Sprintf(shouldNotHaveBeenNil, actual)
}
return success
}
// ShouldBeTrue receives a single parameter and ensures that it is true.
func ShouldBeTrue(actual interface{}, expected ...interface{}) string {
if fail := need(0, expected); fail != success {
return fail
} else if actual != true {
return fmt.Sprintf(shouldHaveBeenTrue, actual)
}
return success
}
// ShouldBeFalse receives a single parameter and ensures that it is false.
func ShouldBeFalse(actual interface{}, expected ...interface{}) string {
if fail := need(0, expected); fail != success {
return fail
} else if actual != false {
return fmt.Sprintf(shouldHaveBeenFalse, actual)
}
return success
}
// ShouldBeZeroValue receives a single parameter and ensures that it is
// the Go equivalent of the default value, or "zero" value.
func ShouldBeZeroValue(actual interface{}, expected ...interface{}) string {
if fail := need(0, expected); fail != success {
return fail
}
zeroVal := reflect.Zero(reflect.TypeOf(actual)).Interface()
if !reflect.DeepEqual(zeroVal, actual) {
return serializer.serialize(zeroVal, actual, fmt.Sprintf(shouldHaveBeenZeroValue, actual))
}
return success
}
package assertions
import "fmt"
const (
success = ""
needExactValues = "This assertion requires exactly %d comparison values (you provided %d)."
)
func need(needed int, expected []interface{}) string {
if len(expected) != needed {
return fmt.Sprintf(needExactValues, needed, len(expected))
}
return success
}
func atLeast(minimum int, expected []interface{}) string {
if len(expected) < 1 {
return shouldHaveProvidedCollectionMembers
}
return success
}
package assertions
var serializer Serializer
func init() {
serializer = newSerializer()
}
package assertions
const ( // equality
shouldHaveBeenEqual = "Expected: '%v'\nActual: '%v'\n(Should be equal)"
shouldNotHaveBeenEqual = "Expected '%v'\nto NOT equal '%v'\n(but it did)!"
shouldHaveBeenAlmostEqual = "Expected '%v' to almost equal '%v' (but it didn't)!"
shouldHaveNotBeenAlmostEqual = "Expected '%v' to NOT almost equal '%v' (but it did)!"
shouldHaveResembled = "Expected: '%T(%+v)'\nActual: '%T(%+v)'\n(Should resemble)!"
shouldNotHaveResembled = "Expected '%T(%+v)'\nto NOT resemble '%T(%+v)'\n(but it did)!"
shouldBePointers = "Both arguments should be pointers "
shouldHaveBeenNonNilPointer = shouldBePointers + "(the %s was %s)!"
shouldHavePointedTo = "Expected '%+v' (address: '%v') and '%+v' (address: '%v') to be the same address (but their weren't)!"
shouldNotHavePointedTo = "Expected '%+v' and '%+v' to be different references (but they matched: '%v')!"
shouldHaveBeenNil = "Expected: nil\nActual: '%v'"
shouldNotHaveBeenNil = "Expected '%+v' to NOT be nil (but it was)!"
shouldHaveBeenTrue = "Expected: true\nActual: %v"
shouldHaveBeenFalse = "Expected: false\nActual: %v"
shouldHaveBeenZeroValue = "'%+v' should have been the zero value" //"Expected: (zero value)\nActual: %v"
)
const ( // quantity comparisons
shouldHaveBeenGreater = "Expected '%v' to be greater than '%v' (but it wasn't)!"
shouldHaveBeenGreaterOrEqual = "Expected '%v' to be greater than or equal to '%v' (but it wasn't)!"
shouldHaveBeenLess = "Expected '%v' to be less than '%v' (but it wasn't)!"
shouldHaveBeenLessOrEqual = "Expected '%v' to be less than or equal to '%v' (but it wasn't)!"
shouldHaveBeenBetween = "Expected '%v' to be between '%v' and '%v' (but it wasn't)!"
shouldNotHaveBeenBetween = "Expected '%v' NOT to be between '%v' and '%v' (but it was)!"
shouldHaveDifferentUpperAndLower = "The lower and upper bounds must be different values (they were both '%v')."
shouldHaveBeenBetweenOrEqual = "Expected '%v' to be between '%v' and '%v' or equal to one of them (but it wasn't)!"
shouldNotHaveBeenBetweenOrEqual = "Expected '%v' NOT to be between '%v' and '%v' or equal to one of them (but it was)!"
)
const ( // collections
shouldHaveContained = "Expected the container (%v) to contain: '%v' (but it didn't)!"
shouldNotHaveContained = "Expected the container (%v) NOT to contain: '%v' (but it did)!"
shouldHaveBeenIn = "Expected '%v' to be in the container (%v, but it wasn't)!"
shouldNotHaveBeenIn = "Expected '%v' NOT to be in the container (%v, but it was)!"
shouldHaveBeenAValidCollection = "You must provide a valid container (was %v)!"
shouldHaveProvidedCollectionMembers = "This assertion requires at least 1 comparison value (you provided 0)."
shouldHaveBeenEmpty = "Expected %+v to be empty (but it wasn't)!"
shouldNotHaveBeenEmpty = "Expected %+v to NOT be empty (but it was)!"
)
const ( // strings
shouldHaveStartedWith = "Expected '%v'\nto start with '%v'\n(but it didn't)!"
shouldNotHaveStartedWith = "Expected '%v'\nNOT to start with '%v'\n(but it did)!"
shouldHaveEndedWith = "Expected '%v'\nto end with '%v'\n(but it didn't)!"
shouldNotHaveEndedWith = "Expected '%v'\nNOT to end with '%v'\n(but it did)!"
shouldBothBeStrings = "Both arguments to this assertion must be strings (you provided %v and %v)."
shouldBeString = "The argument to this assertion must be a string (you provided %v)."
shouldHaveContainedSubstring = "Expected '%s' to contain substring '%s' (but it didn't)!"
shouldNotHaveContainedSubstring = "Expected '%s' NOT to contain substring '%s' (but it didn't)!"
shouldHaveBeenBlank = "Expected '%s' to be blank (but it wasn't)!"
shouldNotHaveBeenBlank = "Expected value to NOT be blank (but it was)!"
)
const ( // panics
shouldUseVoidNiladicFunction = "You must provide a void, niladic function as the first argument!"
shouldHavePanickedWith = "Expected func() to panic with '%v' (but it panicked with '%v')!"
shouldHavePanicked = "Expected func() to panic (but it didn't)!"
shouldNotHavePanicked = "Expected func() NOT to panic (error: '%+v')!"
shouldNotHavePanickedWith = "Expected func() NOT to panic with '%v' (but it did)!"
)
const ( // type checking
shouldHaveBeenA = "Expected '%v' to be: '%v' (but was: '%v')!"
shouldNotHaveBeenA = "Expected '%v' to NOT be: '%v' (but it was)!"
shouldHaveImplemented = "Expected: '%v interface support'\nActual: '%v' does not implement the interface!"
shouldNotHaveImplemented = "Expected '%v'\nto NOT implement '%v'\n(but it did)!"
shouldCompareWithInterfacePointer = "The expected value must be a pointer to an interface type (eg. *fmt.Stringer)"
shouldNotBeNilActual = "The actual value was 'nil' and should be a value or a pointer to a value!"
)
const ( // time comparisons
shouldUseTimes = "You must provide time instances as arguments to this assertion."
shouldUseTimeSlice = "You must provide a slice of time instances as the first argument to this assertion."
shouldUseDurationAndTime = "You must provide a duration and a time as arguments to this assertion."
shouldHaveHappenedBefore = "Expected '%v' to happen before '%v' (it happened '%v' after)!"
shouldHaveHappenedAfter = "Expected '%v' to happen after '%v' (it happened '%v' before)!"
shouldHaveHappenedBetween = "Expected '%v' to happen between '%v' and '%v' (it happened '%v' outside threshold)!"
shouldNotHaveHappenedOnOrBetween = "Expected '%v' to NOT happen on or between '%v' and '%v' (but it did)!"
// format params: incorrect-index, previous-index, previous-time, incorrect-index, incorrect-time
shouldHaveBeenChronological = "The 'Time' at index [%d] should have happened after the previous one (but it didn't!):\n [%d]: %s\n [%d]: %s (see, it happened before!)"
)
package assertions
import "fmt"
// ShouldPanic receives a void, niladic function and expects to recover a panic.
func ShouldPanic(actual interface{}, expected ...interface{}) (message string) {
if fail := need(0, expected); fail != success {
return fail
}
action, _ := actual.(func())
if action == nil {
message = shouldUseVoidNiladicFunction
return
}
defer func() {
recovered := recover()
if recovered == nil {
message = shouldHavePanicked
} else {
message = success
}
}()
action()
return
}
// ShouldNotPanic receives a void, niladic function and expects to execute the function without any panic.
func ShouldNotPanic(actual interface{}, expected ...interface{}) (message string) {
if fail := need(0, expected); fail != success {
return fail
}
action, _ := actual.(func())
if action == nil {
message = shouldUseVoidNiladicFunction
return
}
defer func() {
recovered := recover()
if recovered != nil {
message = fmt.Sprintf(shouldNotHavePanicked, recovered)
} else {
message = success
}
}()
action()
return
}
// ShouldPanicWith receives a void, niladic function and expects to recover a panic with the second argument as the content.
func ShouldPanicWith(actual interface{}, expected ...interface{}) (message string) {
if fail := need(1, expected); fail != success {
return fail
}
action, _ := actual.(func())
if action == nil {
message = shouldUseVoidNiladicFunction
return
}
defer func() {
recovered := recover()
if recovered == nil {
message = shouldHavePanicked
} else {
if equal := ShouldEqual(recovered, expected[0]); equal != success {
message = serializer.serialize(expected[0], recovered, fmt.Sprintf(shouldHavePanickedWith, expected[0], recovered))
} else {
message = success
}
}
}()
action()
return
}
// ShouldNotPanicWith receives a void, niladic function and expects to recover a panic whose content differs from the second argument.
func ShouldNotPanicWith(actual interface{}, expected ...interface{}) (message string) {
if fail := need(1, expected); fail != success {
return fail
}
action, _ := actual.(func())
if action == nil {
message = shouldUseVoidNiladicFunction
return
}
defer func() {
recovered := recover()
if recovered == nil {
message = success
} else {
if equal := ShouldEqual(recovered, expected[0]); equal == success {
message = fmt.Sprintf(shouldNotHavePanickedWith, expected[0])
} else {
message = success
}
}
}()
action()
return
}
package assertions
import (
"fmt"
"testing"
)
func TestShouldPanic(t *testing.T) {
fail(t, so(func() {}, ShouldPanic, 1), "This assertion requires exactly 0 comparison values (you provided 1).")
fail(t, so(func() {}, ShouldPanic, 1, 2, 3), "This assertion requires exactly 0 comparison values (you provided 3).")
fail(t, so(1, ShouldPanic), shouldUseVoidNiladicFunction)
fail(t, so(func(i int) {}, ShouldPanic), shouldUseVoidNiladicFunction)
fail(t, so(func() int { panic("hi") }, ShouldPanic), shouldUseVoidNiladicFunction)
fail(t, so(func() {}, ShouldPanic), shouldHavePanicked)
pass(t, so(func() { panic("hi") }, ShouldPanic))
}
func TestShouldNotPanic(t *testing.T) {
fail(t, so(func() {}, ShouldNotPanic, 1), "This assertion requires exactly 0 comparison values (you provided 1).")
fail(t, so(func() {}, ShouldNotPanic, 1, 2, 3), "This assertion requires exactly 0 comparison values (you provided 3).")
fail(t, so(1, ShouldNotPanic), shouldUseVoidNiladicFunction)
fail(t, so(func(i int) {}, ShouldNotPanic), shouldUseVoidNiladicFunction)
fail(t, so(func() { panic("hi") }, ShouldNotPanic), fmt.Sprintf(shouldNotHavePanicked, "hi"))
pass(t, so(func() {}, ShouldNotPanic))
}
func TestShouldPanicWith(t *testing.T) {
fail(t, so(func() {}, ShouldPanicWith), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so(func() {}, ShouldPanicWith, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).")
fail(t, so(1, ShouldPanicWith, 1), shouldUseVoidNiladicFunction)
fail(t, so(func(i int) {}, ShouldPanicWith, "hi"), shouldUseVoidNiladicFunction)
fail(t, so(func() {}, ShouldPanicWith, "bye"), shouldHavePanicked)
fail(t, so(func() { panic("hi") }, ShouldPanicWith, "bye"), "bye|hi|Expected func() to panic with 'bye' (but it panicked with 'hi')!")
pass(t, so(func() { panic("hi") }, ShouldPanicWith, "hi"))
}
func TestShouldNotPanicWith(t *testing.T) {
fail(t, so(func() {}, ShouldNotPanicWith), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so(func() {}, ShouldNotPanicWith, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).")
fail(t, so(1, ShouldNotPanicWith, 1), shouldUseVoidNiladicFunction)
fail(t, so(func(i int) {}, ShouldNotPanicWith, "hi"), shouldUseVoidNiladicFunction)
fail(t, so(func() { panic("hi") }, ShouldNotPanicWith, "hi"), "Expected func() NOT to panic with 'hi' (but it did)!")
pass(t, so(func() {}, ShouldNotPanicWith, "bye"))
pass(t, so(func() { panic("hi") }, ShouldNotPanicWith, "bye"))
}
package assertions
import (
"fmt"
"github.com/jacobsa/oglematchers"
)
// ShouldBeGreaterThan receives exactly two parameters and ensures that the first is greater than the second.
func ShouldBeGreaterThan(actual interface{}, expected ...interface{}) string {
if fail := need(1, expected); fail != success {
return fail
}
if matchError := oglematchers.GreaterThan(expected[0]).Matches(actual); matchError != nil {
return fmt.Sprintf(shouldHaveBeenGreater, actual, expected[0])
}
return success
}
// ShouldBeGreaterThanOrEqualTo receives exactly two parameters and ensures that the first is greater than or equal to the second.
func ShouldBeGreaterThanOrEqualTo(actual interface{}, expected ...interface{}) string {
if fail := need(1, expected); fail != success {
return fail
} else if matchError := oglematchers.GreaterOrEqual(expected[0]).Matches(actual); matchError != nil {
return fmt.Sprintf(shouldHaveBeenGreaterOrEqual, actual, expected[0])
}
return success
}
// ShouldBeLessThan receives exactly two parameters and ensures that the first is less than the second.
func ShouldBeLessThan(actual interface{}, expected ...interface{}) string {
if fail := need(1, expected); fail != success {
return fail
} else if matchError := oglematchers.LessThan(expected[0]).Matches(actual); matchError != nil {
return fmt.Sprintf(shouldHaveBeenLess, actual, expected[0])
}
return success
}
// ShouldBeLessThan receives exactly two parameters and ensures that the first is less than or equal to the second.
func ShouldBeLessThanOrEqualTo(actual interface{}, expected ...interface{}) string {
if fail := need(1, expected); fail != success {
return fail
} else if matchError := oglematchers.LessOrEqual(expected[0]).Matches(actual); matchError != nil {
return fmt.Sprintf(shouldHaveBeenLess, actual, expected[0])
}
return success
}
// ShouldBeBetween receives exactly three parameters: an actual value, a lower bound, and an upper bound.
// It ensures that the actual value is between both bounds (but not equal to either of them).
func ShouldBeBetween(actual interface{}, expected ...interface{}) string {
if fail := need(2, expected); fail != success {
return fail
}
lower, upper, fail := deriveBounds(expected)
if fail != success {
return fail
} else if !isBetween(actual, lower, upper) {
return fmt.Sprintf(shouldHaveBeenBetween, actual, lower, upper)
}
return success
}
// ShouldNotBeBetween receives exactly three parameters: an actual value, a lower bound, and an upper bound.
// It ensures that the actual value is NOT between both bounds.
func ShouldNotBeBetween(actual interface{}, expected ...interface{}) string {
if fail := need(2, expected); fail != success {
return fail
}
lower, upper, fail := deriveBounds(expected)
if fail != success {
return fail
} else if isBetween(actual, lower, upper) {
return fmt.Sprintf(shouldNotHaveBeenBetween, actual, lower, upper)
}
return success
}
func deriveBounds(values []interface{}) (lower interface{}, upper interface{}, fail string) {
lower = values[0]
upper = values[1]
if ShouldNotEqual(lower, upper) != success {
return nil, nil, fmt.Sprintf(shouldHaveDifferentUpperAndLower, lower)
} else if ShouldBeLessThan(lower, upper) != success {
lower, upper = upper, lower
}
return lower, upper, success
}
func isBetween(value, lower, upper interface{}) bool {
if ShouldBeGreaterThan(value, lower) != success {
return false
} else if ShouldBeLessThan(value, upper) != success {
return false
}
return true
}
// ShouldBeBetweenOrEqual receives exactly three parameters: an actual value, a lower bound, and an upper bound.
// It ensures that the actual value is between both bounds or equal to one of them.
func ShouldBeBetweenOrEqual(actual interface{}, expected ...interface{}) string {
if fail := need(2, expected); fail != success {
return fail
}
lower, upper, fail := deriveBounds(expected)
if fail != success {
return fail
} else if !isBetweenOrEqual(actual, lower, upper) {
return fmt.Sprintf(shouldHaveBeenBetweenOrEqual, actual, lower, upper)
}
return success
}
// ShouldNotBeBetweenOrEqual receives exactly three parameters: an actual value, a lower bound, and an upper bound.
// It ensures that the actual value is nopt between the bounds nor equal to either of them.
func ShouldNotBeBetweenOrEqual(actual interface{}, expected ...interface{}) string {
if fail := need(2, expected); fail != success {
return fail
}
lower, upper, fail := deriveBounds(expected)
if fail != success {
return fail
} else if isBetweenOrEqual(actual, lower, upper) {
return fmt.Sprintf(shouldNotHaveBeenBetweenOrEqual, actual, lower, upper)
}
return success
}
func isBetweenOrEqual(value, lower, upper interface{}) bool {
if ShouldBeGreaterThanOrEqualTo(value, lower) != success {
return false
} else if ShouldBeLessThanOrEqualTo(value, upper) != success {
return false
}
return true
}
package assertions
import "testing"
func TestShouldBeGreaterThan(t *testing.T) {
fail(t, so(1, ShouldBeGreaterThan), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so(1, ShouldBeGreaterThan, 0, 0), "This assertion requires exactly 1 comparison values (you provided 2).")
pass(t, so(1, ShouldBeGreaterThan, 0))
pass(t, so(1.1, ShouldBeGreaterThan, 1))
pass(t, so(1, ShouldBeGreaterThan, uint(0)))
pass(t, so("b", ShouldBeGreaterThan, "a"))
fail(t, so(0, ShouldBeGreaterThan, 1), "Expected '0' to be greater than '1' (but it wasn't)!")
fail(t, so(1, ShouldBeGreaterThan, 1.1), "Expected '1' to be greater than '1.1' (but it wasn't)!")
fail(t, so(uint(0), ShouldBeGreaterThan, 1.1), "Expected '0' to be greater than '1.1' (but it wasn't)!")
fail(t, so("a", ShouldBeGreaterThan, "b"), "Expected 'a' to be greater than 'b' (but it wasn't)!")
}
func TestShouldBeGreaterThanOrEqual(t *testing.T) {
fail(t, so(1, ShouldBeGreaterThanOrEqualTo), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so(1, ShouldBeGreaterThanOrEqualTo, 0, 0), "This assertion requires exactly 1 comparison values (you provided 2).")
pass(t, so(1, ShouldBeGreaterThanOrEqualTo, 1))
pass(t, so(1.1, ShouldBeGreaterThanOrEqualTo, 1.1))
pass(t, so(1, ShouldBeGreaterThanOrEqualTo, uint(1)))
pass(t, so("b", ShouldBeGreaterThanOrEqualTo, "b"))
pass(t, so(1, ShouldBeGreaterThanOrEqualTo, 0))
pass(t, so(1.1, ShouldBeGreaterThanOrEqualTo, 1))
pass(t, so(1, ShouldBeGreaterThanOrEqualTo, uint(0)))
pass(t, so("b", ShouldBeGreaterThanOrEqualTo, "a"))
fail(t, so(0, ShouldBeGreaterThanOrEqualTo, 1), "Expected '0' to be greater than or equal to '1' (but it wasn't)!")
fail(t, so(1, ShouldBeGreaterThanOrEqualTo, 1.1), "Expected '1' to be greater than or equal to '1.1' (but it wasn't)!")
fail(t, so(uint(0), ShouldBeGreaterThanOrEqualTo, 1.1), "Expected '0' to be greater than or equal to '1.1' (but it wasn't)!")
fail(t, so("a", ShouldBeGreaterThanOrEqualTo, "b"), "Expected 'a' to be greater than or equal to 'b' (but it wasn't)!")
}
func TestShouldBeLessThan(t *testing.T) {
fail(t, so(1, ShouldBeLessThan), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so(1, ShouldBeLessThan, 0, 0), "This assertion requires exactly 1 comparison values (you provided 2).")
pass(t, so(0, ShouldBeLessThan, 1))
pass(t, so(1, ShouldBeLessThan, 1.1))
pass(t, so(uint(0), ShouldBeLessThan, 1))
pass(t, so("a", ShouldBeLessThan, "b"))
fail(t, so(1, ShouldBeLessThan, 0), "Expected '1' to be less than '0' (but it wasn't)!")
fail(t, so(1.1, ShouldBeLessThan, 1), "Expected '1.1' to be less than '1' (but it wasn't)!")
fail(t, so(1.1, ShouldBeLessThan, uint(0)), "Expected '1.1' to be less than '0' (but it wasn't)!")
fail(t, so("b", ShouldBeLessThan, "a"), "Expected 'b' to be less than 'a' (but it wasn't)!")
}
func TestShouldBeLessThanOrEqualTo(t *testing.T) {
fail(t, so(1, ShouldBeLessThanOrEqualTo), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so(1, ShouldBeLessThanOrEqualTo, 0, 0), "This assertion requires exactly 1 comparison values (you provided 2).")
pass(t, so(1, ShouldBeLessThanOrEqualTo, 1))
pass(t, so(1.1, ShouldBeLessThanOrEqualTo, 1.1))
pass(t, so(uint(1), ShouldBeLessThanOrEqualTo, 1))
pass(t, so("b", ShouldBeLessThanOrEqualTo, "b"))
pass(t, so(0, ShouldBeLessThanOrEqualTo, 1))
pass(t, so(1, ShouldBeLessThanOrEqualTo, 1.1))
pass(t, so(uint(0), ShouldBeLessThanOrEqualTo, 1))
pass(t, so("a", ShouldBeLessThanOrEqualTo, "b"))
fail(t, so(1, ShouldBeLessThanOrEqualTo, 0), "Expected '1' to be less than '0' (but it wasn't)!")
fail(t, so(1.1, ShouldBeLessThanOrEqualTo, 1), "Expected '1.1' to be less than '1' (but it wasn't)!")
fail(t, so(1.1, ShouldBeLessThanOrEqualTo, uint(0)), "Expected '1.1' to be less than '0' (but it wasn't)!")
fail(t, so("b", ShouldBeLessThanOrEqualTo, "a"), "Expected 'b' to be less than 'a' (but it wasn't)!")
}
func TestShouldBeBetween(t *testing.T) {
fail(t, so(1, ShouldBeBetween), "This assertion requires exactly 2 comparison values (you provided 0).")
fail(t, so(1, ShouldBeBetween, 1, 2, 3), "This assertion requires exactly 2 comparison values (you provided 3).")
fail(t, so(4, ShouldBeBetween, 1, 1), "The lower and upper bounds must be different values (they were both '1').")
fail(t, so(7, ShouldBeBetween, 8, 12), "Expected '7' to be between '8' and '12' (but it wasn't)!")
fail(t, so(8, ShouldBeBetween, 8, 12), "Expected '8' to be between '8' and '12' (but it wasn't)!")
pass(t, so(9, ShouldBeBetween, 8, 12))
pass(t, so(10, ShouldBeBetween, 8, 12))
pass(t, so(11, ShouldBeBetween, 8, 12))
fail(t, so(12, ShouldBeBetween, 8, 12), "Expected '12' to be between '8' and '12' (but it wasn't)!")
fail(t, so(13, ShouldBeBetween, 8, 12), "Expected '13' to be between '8' and '12' (but it wasn't)!")
pass(t, so(1, ShouldBeBetween, 2, 0))
fail(t, so(-1, ShouldBeBetween, 2, 0), "Expected '-1' to be between '0' and '2' (but it wasn't)!")
}
func TestShouldNotBeBetween(t *testing.T) {
fail(t, so(1, ShouldNotBeBetween), "This assertion requires exactly 2 comparison values (you provided 0).")
fail(t, so(1, ShouldNotBeBetween, 1, 2, 3), "This assertion requires exactly 2 comparison values (you provided 3).")
fail(t, so(4, ShouldNotBeBetween, 1, 1), "The lower and upper bounds must be different values (they were both '1').")
pass(t, so(7, ShouldNotBeBetween, 8, 12))
pass(t, so(8, ShouldNotBeBetween, 8, 12))
fail(t, so(9, ShouldNotBeBetween, 8, 12), "Expected '9' NOT to be between '8' and '12' (but it was)!")
fail(t, so(10, ShouldNotBeBetween, 8, 12), "Expected '10' NOT to be between '8' and '12' (but it was)!")
fail(t, so(11, ShouldNotBeBetween, 8, 12), "Expected '11' NOT to be between '8' and '12' (but it was)!")
pass(t, so(12, ShouldNotBeBetween, 8, 12))
pass(t, so(13, ShouldNotBeBetween, 8, 12))
pass(t, so(-1, ShouldNotBeBetween, 2, 0))
fail(t, so(1, ShouldNotBeBetween, 2, 0), "Expected '1' NOT to be between '0' and '2' (but it was)!")
}
func TestShouldBeBetweenOrEqual(t *testing.T) {
fail(t, so(1, ShouldBeBetweenOrEqual), "This assertion requires exactly 2 comparison values (you provided 0).")
fail(t, so(1, ShouldBeBetweenOrEqual, 1, 2, 3), "This assertion requires exactly 2 comparison values (you provided 3).")
fail(t, so(4, ShouldBeBetweenOrEqual, 1, 1), "The lower and upper bounds must be different values (they were both '1').")
fail(t, so(7, ShouldBeBetweenOrEqual, 8, 12), "Expected '7' to be between '8' and '12' or equal to one of them (but it wasn't)!")
pass(t, so(8, ShouldBeBetweenOrEqual, 8, 12))
pass(t, so(9, ShouldBeBetweenOrEqual, 8, 12))
pass(t, so(10, ShouldBeBetweenOrEqual, 8, 12))
pass(t, so(11, ShouldBeBetweenOrEqual, 8, 12))
pass(t, so(12, ShouldBeBetweenOrEqual, 8, 12))
fail(t, so(13, ShouldBeBetweenOrEqual, 8, 12), "Expected '13' to be between '8' and '12' or equal to one of them (but it wasn't)!")
pass(t, so(1, ShouldBeBetweenOrEqual, 2, 0))
fail(t, so(-1, ShouldBeBetweenOrEqual, 2, 0), "Expected '-1' to be between '0' and '2' or equal to one of them (but it wasn't)!")
}
func TestShouldNotBeBetweenOrEqual(t *testing.T) {
fail(t, so(1, ShouldNotBeBetweenOrEqual), "This assertion requires exactly 2 comparison values (you provided 0).")
fail(t, so(1, ShouldNotBeBetweenOrEqual, 1, 2, 3), "This assertion requires exactly 2 comparison values (you provided 3).")
fail(t, so(4, ShouldNotBeBetweenOrEqual, 1, 1), "The lower and upper bounds must be different values (they were both '1').")
pass(t, so(7, ShouldNotBeBetweenOrEqual, 8, 12))
fail(t, so(8, ShouldNotBeBetweenOrEqual, 8, 12), "Expected '8' NOT to be between '8' and '12' or equal to one of them (but it was)!")
fail(t, so(9, ShouldNotBeBetweenOrEqual, 8, 12), "Expected '9' NOT to be between '8' and '12' or equal to one of them (but it was)!")
fail(t, so(10, ShouldNotBeBetweenOrEqual, 8, 12), "Expected '10' NOT to be between '8' and '12' or equal to one of them (but it was)!")
fail(t, so(11, ShouldNotBeBetweenOrEqual, 8, 12), "Expected '11' NOT to be between '8' and '12' or equal to one of them (but it was)!")
fail(t, so(12, ShouldNotBeBetweenOrEqual, 8, 12), "Expected '12' NOT to be between '8' and '12' or equal to one of them (but it was)!")
pass(t, so(13, ShouldNotBeBetweenOrEqual, 8, 12))
pass(t, so(-1, ShouldNotBeBetweenOrEqual, 2, 0))
fail(t, so(1, ShouldNotBeBetweenOrEqual, 2, 0), "Expected '1' NOT to be between '0' and '2' or equal to one of them (but it was)!")
}
package assertions
import (
"encoding/json"
"fmt"
"github.com/smartystreets/goconvey/convey/reporting"
)
type Serializer interface {
serialize(expected, actual interface{}, message string) string
}
type failureSerializer struct{}
func (self *failureSerializer) serialize(expected, actual interface{}, message string) string {
view := reporting.FailureView{
Message: message,
Expected: fmt.Sprintf("%+v", expected),
Actual: fmt.Sprintf("%+v", actual),
}
serialized, err := json.Marshal(view)
if err != nil {
return message
}
return string(serialized)
}
func newSerializer() *failureSerializer {
return &failureSerializer{}
}
package assertions
import (
"encoding/json"
"fmt"
"testing"
"github.com/smartystreets/goconvey/convey/reporting"
)
func TestSerializerCreatesSerializedVersionOfAssertionResult(t *testing.T) {
thing1 := Thing1{"Hi"}
thing2 := Thing2{"Bye"}
message := "Super-hip failure message."
serializer := newSerializer()
actualResult := serializer.serialize(thing1, thing2, message)
expectedResult, _ := json.Marshal(reporting.FailureView{
Message: message,
Expected: fmt.Sprintf("%+v", thing1),
Actual: fmt.Sprintf("%+v", thing2),
})
if actualResult != string(expectedResult) {
t.Errorf("\nExpected: %s\nActual: %s", string(expectedResult), actualResult)
}
}
package assertions
import (
"fmt"
"reflect"
"strings"
)
// ShouldStartWith receives exactly 2 string parameters and ensures that the first starts with the second.
func ShouldStartWith(actual interface{}, expected ...interface{}) string {
if fail := need(1, expected); fail != success {
return fail
}
value, valueIsString := actual.(string)
prefix, prefixIsString := expected[0].(string)
if !valueIsString || !prefixIsString {
return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0]))
}
return shouldStartWith(value, prefix)
}
func shouldStartWith(value, prefix string) string {
if !strings.HasPrefix(value, prefix) {
shortval := value
if len(shortval) > len(prefix) {
shortval = shortval[:len(prefix)] + "..."
}
return serializer.serialize(prefix, shortval, fmt.Sprintf(shouldHaveStartedWith, value, prefix))
}
return success
}
// ShouldNotStartWith receives exactly 2 string parameters and ensures that the first does not start with the second.
func ShouldNotStartWith(actual interface{}, expected ...interface{}) string {
if fail := need(1, expected); fail != success {
return fail
}
value, valueIsString := actual.(string)
prefix, prefixIsString := expected[0].(string)
if !valueIsString || !prefixIsString {
return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0]))
}
return shouldNotStartWith(value, prefix)
}
func shouldNotStartWith(value, prefix string) string {
if strings.HasPrefix(value, prefix) {
if value == "" {
value = "<empty>"
}
if prefix == "" {
prefix = "<empty>"
}
return fmt.Sprintf(shouldNotHaveStartedWith, value, prefix)
}
return success
}
// ShouldEndWith receives exactly 2 string parameters and ensures that the first ends with the second.
func ShouldEndWith(actual interface{}, expected ...interface{}) string {
if fail := need(1, expected); fail != success {
return fail
}
value, valueIsString := actual.(string)
suffix, suffixIsString := expected[0].(string)
if !valueIsString || !suffixIsString {
return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0]))
}
return shouldEndWith(value, suffix)
}
func shouldEndWith(value, suffix string) string {
if !strings.HasSuffix(value, suffix) {
shortval := value
if len(shortval) > len(suffix) {
shortval = "..." + shortval[len(shortval)-len(suffix):]
}
return serializer.serialize(suffix, shortval, fmt.Sprintf(shouldHaveEndedWith, value, suffix))
}
return success
}
// ShouldEndWith receives exactly 2 string parameters and ensures that the first does not end with the second.
func ShouldNotEndWith(actual interface{}, expected ...interface{}) string {
if fail := need(1, expected); fail != success {
return fail
}
value, valueIsString := actual.(string)
suffix, suffixIsString := expected[0].(string)
if !valueIsString || !suffixIsString {
return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0]))
}
return shouldNotEndWith(value, suffix)
}
func shouldNotEndWith(value, suffix string) string {
if strings.HasSuffix(value, suffix) {
if value == "" {
value = "<empty>"
}
if suffix == "" {
suffix = "<empty>"
}
return fmt.Sprintf(shouldNotHaveEndedWith, value, suffix)
}
return success
}
// ShouldContainSubstring receives exactly 2 string parameters and ensures that the first contains the second as a substring.
func ShouldContainSubstring(actual interface{}, expected ...interface{}) string {
if fail := need(1, expected); fail != success {
return fail
}
long, longOk := actual.(string)
short, shortOk := expected[0].(string)
if !longOk || !shortOk {
return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0]))
}
if !strings.Contains(long, short) {
return serializer.serialize(expected[0], actual, fmt.Sprintf(shouldHaveContainedSubstring, long, short))
}
return success
}
// ShouldNotContainSubstring receives exactly 2 string parameters and ensures that the first does NOT contain the second as a substring.
func ShouldNotContainSubstring(actual interface{}, expected ...interface{}) string {
if fail := need(1, expected); fail != success {
return fail
}
long, longOk := actual.(string)
short, shortOk := expected[0].(string)
if !longOk || !shortOk {
return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0]))
}
if strings.Contains(long, short) {
return fmt.Sprintf(shouldNotHaveContainedSubstring, long, short)
}
return success
}
// ShouldBeBlank receives exactly 1 string parameter and ensures that it is equal to "".
func ShouldBeBlank(actual interface{}, expected ...interface{}) string {
if fail := need(0, expected); fail != success {
return fail
}
value, ok := actual.(string)
if !ok {
return fmt.Sprintf(shouldBeString, reflect.TypeOf(actual))
}
if value != "" {
return serializer.serialize("", value, fmt.Sprintf(shouldHaveBeenBlank, value))
}
return success
}
// ShouldNotBeBlank receives exactly 1 string parameter and ensures that it is equal to "".
func ShouldNotBeBlank(actual interface{}, expected ...interface{}) string {
if fail := need(0, expected); fail != success {
return fail
}
value, ok := actual.(string)
if !ok {
return fmt.Sprintf(shouldBeString, reflect.TypeOf(actual))
}
if value == "" {
return shouldNotHaveBeenBlank
}
return success
}
package assertions
import "testing"
func TestShouldStartWith(t *testing.T) {
serializer = newFakeSerializer()
fail(t, so("", ShouldStartWith), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so("", ShouldStartWith, "asdf", "asdf"), "This assertion requires exactly 1 comparison values (you provided 2).")
pass(t, so("", ShouldStartWith, ""))
fail(t, so("", ShouldStartWith, "x"), "x||Expected '' to start with 'x' (but it didn't)!")
pass(t, so("abc", ShouldStartWith, "abc"))
fail(t, so("abc", ShouldStartWith, "abcd"), "abcd|abc|Expected 'abc' to start with 'abcd' (but it didn't)!")
pass(t, so("superman", ShouldStartWith, "super"))
fail(t, so("superman", ShouldStartWith, "bat"), "bat|sup...|Expected 'superman' to start with 'bat' (but it didn't)!")
fail(t, so("superman", ShouldStartWith, "man"), "man|sup...|Expected 'superman' to start with 'man' (but it didn't)!")
fail(t, so(1, ShouldStartWith, 2), "Both arguments to this assertion must be strings (you provided int and int).")
}
func TestShouldNotStartWith(t *testing.T) {
fail(t, so("", ShouldNotStartWith), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so("", ShouldNotStartWith, "asdf", "asdf"), "This assertion requires exactly 1 comparison values (you provided 2).")
fail(t, so("", ShouldNotStartWith, ""), "Expected '<empty>' NOT to start with '<empty>' (but it did)!")
fail(t, so("superman", ShouldNotStartWith, "super"), "Expected 'superman' NOT to start with 'super' (but it did)!")
pass(t, so("superman", ShouldNotStartWith, "bat"))
pass(t, so("superman", ShouldNotStartWith, "man"))
fail(t, so(1, ShouldNotStartWith, 2), "Both arguments to this assertion must be strings (you provided int and int).")
}
func TestShouldEndWith(t *testing.T) {
serializer = newFakeSerializer()
fail(t, so("", ShouldEndWith), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so("", ShouldEndWith, "", ""), "This assertion requires exactly 1 comparison values (you provided 2).")
pass(t, so("", ShouldEndWith, ""))
fail(t, so("", ShouldEndWith, "z"), "z||Expected '' to end with 'z' (but it didn't)!")
pass(t, so("xyz", ShouldEndWith, "xyz"))
fail(t, so("xyz", ShouldEndWith, "wxyz"), "wxyz|xyz|Expected 'xyz' to end with 'wxyz' (but it didn't)!")
pass(t, so("superman", ShouldEndWith, "man"))
fail(t, so("superman", ShouldEndWith, "super"), "super|...erman|Expected 'superman' to end with 'super' (but it didn't)!")
fail(t, so("superman", ShouldEndWith, "blah"), "blah|...rman|Expected 'superman' to end with 'blah' (but it didn't)!")
fail(t, so(1, ShouldEndWith, 2), "Both arguments to this assertion must be strings (you provided int and int).")
}
func TestShouldNotEndWith(t *testing.T) {
fail(t, so("", ShouldNotEndWith), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so("", ShouldNotEndWith, "", ""), "This assertion requires exactly 1 comparison values (you provided 2).")
fail(t, so("", ShouldNotEndWith, ""), "Expected '<empty>' NOT to end with '<empty>' (but it did)!")
fail(t, so("superman", ShouldNotEndWith, "man"), "Expected 'superman' NOT to end with 'man' (but it did)!")
pass(t, so("superman", ShouldNotEndWith, "super"))
fail(t, so(1, ShouldNotEndWith, 2), "Both arguments to this assertion must be strings (you provided int and int).")
}
func TestShouldContainSubstring(t *testing.T) {
serializer = newFakeSerializer()
fail(t, so("asdf", ShouldContainSubstring), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so("asdf", ShouldContainSubstring, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).")
fail(t, so(123, ShouldContainSubstring, 23), "Both arguments to this assertion must be strings (you provided int and int).")
pass(t, so("asdf", ShouldContainSubstring, "sd"))
fail(t, so("qwer", ShouldContainSubstring, "sd"), "sd|qwer|Expected 'qwer' to contain substring 'sd' (but it didn't)!")
}
func TestShouldNotContainSubstring(t *testing.T) {
fail(t, so("asdf", ShouldNotContainSubstring), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so("asdf", ShouldNotContainSubstring, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).")
fail(t, so(123, ShouldNotContainSubstring, 23), "Both arguments to this assertion must be strings (you provided int and int).")
pass(t, so("qwer", ShouldNotContainSubstring, "sd"))
fail(t, so("asdf", ShouldNotContainSubstring, "sd"), "Expected 'asdf' NOT to contain substring 'sd' (but it didn't)!")
}
func TestShouldBeBlank(t *testing.T) {
serializer = newFakeSerializer()
fail(t, so("", ShouldBeBlank, "adsf"), "This assertion requires exactly 0 comparison values (you provided 1).")
fail(t, so(1, ShouldBeBlank), "The argument to this assertion must be a string (you provided int).")
fail(t, so("asdf", ShouldBeBlank), "|asdf|Expected 'asdf' to be blank (but it wasn't)!")
pass(t, so("", ShouldBeBlank))
}
func TestShouldNotBeBlank(t *testing.T) {
fail(t, so("", ShouldNotBeBlank, "adsf"), "This assertion requires exactly 0 comparison values (you provided 1).")
fail(t, so(1, ShouldNotBeBlank), "The argument to this assertion must be a string (you provided int).")
fail(t, so("", ShouldNotBeBlank), "Expected value to NOT be blank (but it was)!")
pass(t, so("asdf", ShouldNotBeBlank))
}
package assertions
import (
"fmt"
"time"
)
// ShouldHappenBefore receives exactly 2 time.Time arguments and asserts that the first happens before the second.
func ShouldHappenBefore(actual interface{}, expected ...interface{}) string {
if fail := need(1, expected); fail != success {
return fail
}
actualTime, firstOk := actual.(time.Time)
expectedTime, secondOk := expected[0].(time.Time)
if !firstOk || !secondOk {
return shouldUseTimes
}
if !actualTime.Before(expectedTime) {
return fmt.Sprintf(shouldHaveHappenedBefore, actualTime, expectedTime, actualTime.Sub(expectedTime))
}
return success
}
// ShouldHappenOnOrBefore receives exactly 2 time.Time arguments and asserts that the first happens on or before the second.
func ShouldHappenOnOrBefore(actual interface{}, expected ...interface{}) string {
if fail := need(1, expected); fail != success {
return fail
}
actualTime, firstOk := actual.(time.Time)
expectedTime, secondOk := expected[0].(time.Time)
if !firstOk || !secondOk {
return shouldUseTimes
}
if actualTime.Equal(expectedTime) {
return success
}
return ShouldHappenBefore(actualTime, expectedTime)
}
// ShouldHappenAfter receives exactly 2 time.Time arguments and asserts that the first happens after the second.
func ShouldHappenAfter(actual interface{}, expected ...interface{}) string {
if fail := need(1, expected); fail != success {
return fail
}
actualTime, firstOk := actual.(time.Time)
expectedTime, secondOk := expected[0].(time.Time)
if !firstOk || !secondOk {
return shouldUseTimes
}
if !actualTime.After(expectedTime) {
return fmt.Sprintf(shouldHaveHappenedAfter, actualTime, expectedTime, expectedTime.Sub(actualTime))
}
return success
}
// ShouldHappenOnOrAfter receives exactly 2 time.Time arguments and asserts that the first happens on or after the second.
func ShouldHappenOnOrAfter(actual interface{}, expected ...interface{}) string {
if fail := need(1, expected); fail != success {
return fail
}
actualTime, firstOk := actual.(time.Time)
expectedTime, secondOk := expected[0].(time.Time)
if !firstOk || !secondOk {
return shouldUseTimes
}
if actualTime.Equal(expectedTime) {
return success
}
return ShouldHappenAfter(actualTime, expectedTime)
}
// ShouldHappenBetween receives exactly 3 time.Time arguments and asserts that the first happens between (not on) the second and third.
func ShouldHappenBetween(actual interface{}, expected ...interface{}) string {
if fail := need(2, expected); fail != success {
return fail
}
actualTime, firstOk := actual.(time.Time)
min, secondOk := expected[0].(time.Time)
max, thirdOk := expected[1].(time.Time)
if !firstOk || !secondOk || !thirdOk {
return shouldUseTimes
}
if !actualTime.After(min) {
return fmt.Sprintf(shouldHaveHappenedBetween, actualTime, min, max, min.Sub(actualTime))
}
if !actualTime.Before(max) {
return fmt.Sprintf(shouldHaveHappenedBetween, actualTime, min, max, actualTime.Sub(max))
}
return success
}
// ShouldHappenOnOrBetween receives exactly 3 time.Time arguments and asserts that the first happens between or on the second and third.
func ShouldHappenOnOrBetween(actual interface{}, expected ...interface{}) string {
if fail := need(2, expected); fail != success {
return fail
}
actualTime, firstOk := actual.(time.Time)
min, secondOk := expected[0].(time.Time)
max, thirdOk := expected[1].(time.Time)
if !firstOk || !secondOk || !thirdOk {
return shouldUseTimes
}
if actualTime.Equal(min) || actualTime.Equal(max) {
return success
}
return ShouldHappenBetween(actualTime, min, max)
}
// ShouldNotHappenOnOrBetween receives exactly 3 time.Time arguments and asserts that the first
// does NOT happen between or on the second or third.
func ShouldNotHappenOnOrBetween(actual interface{}, expected ...interface{}) string {
if fail := need(2, expected); fail != success {
return fail
}
actualTime, firstOk := actual.(time.Time)
min, secondOk := expected[0].(time.Time)
max, thirdOk := expected[1].(time.Time)
if !firstOk || !secondOk || !thirdOk {
return shouldUseTimes
}
if actualTime.Equal(min) || actualTime.Equal(max) {
return fmt.Sprintf(shouldNotHaveHappenedOnOrBetween, actualTime, min, max)
}
if actualTime.After(min) && actualTime.Before(max) {
return fmt.Sprintf(shouldNotHaveHappenedOnOrBetween, actualTime, min, max)
}
return success
}
// ShouldHappenWithin receives a time.Time, a time.Duration, and a time.Time (3 arguments)
// and asserts that the first time.Time happens within or on the duration specified relative to
// the other time.Time.
func ShouldHappenWithin(actual interface{}, expected ...interface{}) string {
if fail := need(2, expected); fail != success {
return fail
}
actualTime, firstOk := actual.(time.Time)
tolerance, secondOk := expected[0].(time.Duration)
threshold, thirdOk := expected[1].(time.Time)
if !firstOk || !secondOk || !thirdOk {
return shouldUseDurationAndTime
}
min := threshold.Add(-tolerance)
max := threshold.Add(tolerance)
return ShouldHappenOnOrBetween(actualTime, min, max)
}
// ShouldNotHappenWithin receives a time.Time, a time.Duration, and a time.Time (3 arguments)
// and asserts that the first time.Time does NOT happen within or on the duration specified relative to
// the other time.Time.
func ShouldNotHappenWithin(actual interface{}, expected ...interface{}) string {
if fail := need(2, expected); fail != success {
return fail
}
actualTime, firstOk := actual.(time.Time)
tolerance, secondOk := expected[0].(time.Duration)
threshold, thirdOk := expected[1].(time.Time)
if !firstOk || !secondOk || !thirdOk {
return shouldUseDurationAndTime
}
min := threshold.Add(-tolerance)
max := threshold.Add(tolerance)
return ShouldNotHappenOnOrBetween(actualTime, min, max)
}
// ShouldBeChronological receives a []time.Time slice and asserts that the are
// in chronological order starting with the first time.Time as the earliest.
func ShouldBeChronological(actual interface{}, expected ...interface{}) string {
if fail := need(0, expected); fail != success {
return fail
}
times, ok := actual.([]time.Time)
if !ok {
return shouldUseTimeSlice
}
var previous time.Time
for i, current := range times {
if i > 0 && current.Before(previous) {
return fmt.Sprintf(shouldHaveBeenChronological,
i, i-1, previous.String(), i, current.String())
}
previous = current
}
return ""
}
package assertions
import (
"fmt"
"reflect"
)
// ShouldHaveSameTypeAs receives exactly two parameters and compares their underlying types for equality.
func ShouldHaveSameTypeAs(actual interface{}, expected ...interface{}) string {
if fail := need(1, expected); fail != success {
return fail
}
first := reflect.TypeOf(actual)
second := reflect.TypeOf(expected[0])
if equal := ShouldEqual(first, second); equal != success {
return serializer.serialize(second, first, fmt.Sprintf(shouldHaveBeenA, actual, second, first))
}
return success
}
// ShouldNotHaveSameTypeAs receives exactly two parameters and compares their underlying types for inequality.
func ShouldNotHaveSameTypeAs(actual interface{}, expected ...interface{}) string {
if fail := need(1, expected); fail != success {
return fail
}
first := reflect.TypeOf(actual)
second := reflect.TypeOf(expected[0])
if equal := ShouldEqual(first, second); equal == success {
return fmt.Sprintf(shouldNotHaveBeenA, actual, second)
}
return success
}
// ShouldImplement receives exactly two parameters and ensures
// that the first implements the interface type of the second.
func ShouldImplement(actual interface{}, expectedList ...interface{}) string {
if fail := need(1, expectedList); fail != success {
return fail
}
expected := expectedList[0]
if fail := ShouldBeNil(expected); fail != success {
return shouldCompareWithInterfacePointer
}
if fail := ShouldNotBeNil(actual); fail != success {
return shouldNotBeNilActual
}
var actualType reflect.Type
if reflect.TypeOf(actual).Kind() != reflect.Ptr {
actualType = reflect.PtrTo(reflect.TypeOf(actual))
} else {
actualType = reflect.TypeOf(actual)
}
expectedType := reflect.TypeOf(expected)
if fail := ShouldNotBeNil(expectedType); fail != success {
return shouldCompareWithInterfacePointer
}
expectedInterface := expectedType.Elem()
if actualType == nil {
return fmt.Sprintf(shouldHaveImplemented, expectedInterface, actual)
}
if !actualType.Implements(expectedInterface) {
return fmt.Sprintf(shouldHaveImplemented, expectedInterface, actualType)
}
return success
}
// ShouldNotImplement receives exactly two parameters and ensures
// that the first does NOT implement the interface type of the second.
func ShouldNotImplement(actual interface{}, expectedList ...interface{}) string {
if fail := need(1, expectedList); fail != success {
return fail
}
expected := expectedList[0]
if fail := ShouldBeNil(expected); fail != success {
return shouldCompareWithInterfacePointer
}
if fail := ShouldNotBeNil(actual); fail != success {
return shouldNotBeNilActual
}
var actualType reflect.Type
if reflect.TypeOf(actual).Kind() != reflect.Ptr {
actualType = reflect.PtrTo(reflect.TypeOf(actual))
} else {
actualType = reflect.TypeOf(actual)
}
expectedType := reflect.TypeOf(expected)
if fail := ShouldNotBeNil(expectedType); fail != success {
return shouldCompareWithInterfacePointer
}
expectedInterface := expectedType.Elem()
if actualType.Implements(expectedInterface) {
return fmt.Sprintf(shouldNotHaveImplemented, actualType, expectedInterface)
}
return success
}
package assertions
import (
"bytes"
"io"
"net/http"
"testing"
)
func TestShouldHaveSameTypeAs(t *testing.T) {
serializer = newFakeSerializer()
fail(t, so(1, ShouldHaveSameTypeAs), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so(1, ShouldHaveSameTypeAs, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).")
fail(t, so(nil, ShouldHaveSameTypeAs, 0), "int|<nil>|Expected '<nil>' to be: 'int' (but was: '<nil>')!")
fail(t, so(1, ShouldHaveSameTypeAs, "asdf"), "string|int|Expected '1' to be: 'string' (but was: 'int')!")
pass(t, so(1, ShouldHaveSameTypeAs, 0))
pass(t, so(nil, ShouldHaveSameTypeAs, nil))
}
func TestShouldNotHaveSameTypeAs(t *testing.T) {
fail(t, so(1, ShouldNotHaveSameTypeAs), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so(1, ShouldNotHaveSameTypeAs, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).")
fail(t, so(1, ShouldNotHaveSameTypeAs, 0), "Expected '1' to NOT be: 'int' (but it was)!")
fail(t, so(nil, ShouldNotHaveSameTypeAs, nil), "Expected '<nil>' to NOT be: '<nil>' (but it was)!")
pass(t, so(nil, ShouldNotHaveSameTypeAs, 0))
pass(t, so(1, ShouldNotHaveSameTypeAs, "asdf"))
}
func TestShouldImplement(t *testing.T) {
var ioReader *io.Reader = nil
var response http.Response = http.Response{}
var responsePtr *http.Response = new(http.Response)
var reader = bytes.NewBufferString("")
fail(t, so(reader, ShouldImplement), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so(reader, ShouldImplement, ioReader, ioReader), "This assertion requires exactly 1 comparison values (you provided 2).")
fail(t, so(reader, ShouldImplement, ioReader, ioReader, ioReader), "This assertion requires exactly 1 comparison values (you provided 3).")
fail(t, so(reader, ShouldImplement, "foo"), shouldCompareWithInterfacePointer)
fail(t, so(reader, ShouldImplement, 1), shouldCompareWithInterfacePointer)
fail(t, so(reader, ShouldImplement, nil), shouldCompareWithInterfacePointer)
fail(t, so(nil, ShouldImplement, ioReader), shouldNotBeNilActual)
fail(t, so(1, ShouldImplement, ioReader), "Expected: 'io.Reader interface support'\nActual: '*int' does not implement the interface!")
fail(t, so(response, ShouldImplement, ioReader), "Expected: 'io.Reader interface support'\nActual: '*http.Response' does not implement the interface!")
fail(t, so(responsePtr, ShouldImplement, ioReader), "Expected: 'io.Reader interface support'\nActual: '*http.Response' does not implement the interface!")
pass(t, so(reader, ShouldImplement, ioReader))
pass(t, so(reader, ShouldImplement, (*io.Reader)(nil)))
}
func TestShouldNotImplement(t *testing.T) {
var ioReader *io.Reader = nil
var response http.Response = http.Response{}
var responsePtr *http.Response = new(http.Response)
var reader io.Reader = bytes.NewBufferString("")
fail(t, so(reader, ShouldNotImplement), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so(reader, ShouldNotImplement, ioReader, ioReader), "This assertion requires exactly 1 comparison values (you provided 2).")
fail(t, so(reader, ShouldNotImplement, ioReader, ioReader, ioReader), "This assertion requires exactly 1 comparison values (you provided 3).")
fail(t, so(reader, ShouldNotImplement, "foo"), shouldCompareWithInterfacePointer)
fail(t, so(reader, ShouldNotImplement, 1), shouldCompareWithInterfacePointer)
fail(t, so(reader, ShouldNotImplement, nil), shouldCompareWithInterfacePointer)
fail(t, so(reader, ShouldNotImplement, ioReader), "Expected '*bytes.Buffer'\nto NOT implement 'io.Reader' (but it did)!")
fail(t, so(nil, ShouldNotImplement, ioReader), shouldNotBeNilActual)
pass(t, so(1, ShouldNotImplement, ioReader))
pass(t, so(response, ShouldNotImplement, ioReader))
pass(t, so(responsePtr, ShouldNotImplement, ioReader))
}
package assertions
import (
"fmt"
"path"
"runtime"
"strings"
"testing"
)
func pass(t *testing.T, result string) {
if result != success {
_, file, line, _ := runtime.Caller(1)
base := path.Base(file)
t.Errorf("Expectation should have passed but failed (see %s: line %d): '%s'", base, line, result)
}
}
func fail(t *testing.T, actual string, expected string) {
actual = format(actual)
expected = format(expected)
if actual != expected {
if actual == "" {
actual = "(empty)"
}
_, file, line, _ := runtime.Caller(1)
base := path.Base(file)
t.Errorf("Expectation should have failed but passed (see %s: line %d). \nExpected: %s\nActual: %s\n",
base, line, expected, actual)
}
}
func format(message string) string {
message = strings.Replace(message, "\n", " ", -1)
for strings.Contains(message, " ") {
message = strings.Replace(message, " ", " ", -1)
}
return message
}
func so(actual interface{}, assert func(interface{}, ...interface{}) string, expected ...interface{}) string {
return assert(actual, expected...)
}
type Thing1 struct {
a string
}
type Thing2 struct {
a string
}
type Thinger interface {
Hi()
}
type Thing struct{}
func (self *Thing) Hi() {}
type IntAlias int
type StringAlias string
type StringSliceAlias []string
type StringStringMapAlias map[string]string
/******** FakeSerialzier ********/
type fakeSerializer struct{}
func (self *fakeSerializer) serialize(expected, actual interface{}, message string) string {
return fmt.Sprintf("%v|%v|%s", expected, actual, message)
}
func newFakeSerializer() *fakeSerializer {
return new(fakeSerializer)
}
// Oh the stack trace scanning!
// The density of comments in this file is evidence that
// the code doesn't exactly explain itself. Tread with care...
package convey
import (
"errors"
"fmt"
"runtime"
"strconv"
"strings"
"sync"
)
const (
missingGoTest string = `Top-level calls to Convey(...) need a reference to the *testing.T.
Hint: Convey("description here", t, func() { /* notice that the second argument was the *testing.T (t)! */ }) `
extraGoTest string = `Only the top-level call to Convey(...) needs a reference to the *testing.T.`
)
// suiteContext magically handles all coordination of reporter, runners as they handle calls
// to Convey, So, and the like. It does this via runtime call stack inspection, making sure
// that each test function has its own runner, and routes all live registrations
// to the appropriate runner.
type suiteContext struct {
lock sync.Mutex
runners map[string]*runner // key: testName;
// stores a correlation to the actual runner for outside-of-stack scenaios
locations map[string]string // key: file:line; value: testName (key to runners)
}
func (self *suiteContext) Run(entry *registration) {
if self.current() != nil {
panic(extraGoTest)
}
runner := newRunner(buildReporter())
testName, location, _ := suiteAnchor()
self.setRunner(location, testName, runner)
runner.Run(entry)
self.unsetRunner(location, testName)
}
func (self *suiteContext) Current() *runner {
if runner := self.current(); runner != nil {
return runner
}
panic(missingGoTest)
}
func (self *suiteContext) current() *runner {
self.lock.Lock()
defer self.lock.Unlock()
if testName, _, err := suiteAnchor(); err == nil {
return self.runners[testName]
}
return self.runners[correlate(self.locations)]
}
func (self *suiteContext) setRunner(location string, testName string, runner *runner) {
self.lock.Lock()
defer self.lock.Unlock()
self.locations[location] = testName
self.runners[testName] = runner
}
func (self *suiteContext) unsetRunner(location string, testName string) {
self.lock.Lock()
defer self.lock.Unlock()
delete(self.locations, location)
delete(self.runners, testName)
}
func newSuiteContext() *suiteContext {
return &suiteContext{
locations: map[string]string{},
runners: map[string]*runner{},
}
}
//////////////////// Helper Functions ///////////////////////
// suiteAnchor returns the enclosing test function name (including package) and the
// file:line combination of the top-level Convey. It does this by traversing the
// call stack in reverse, looking for the go testing harnass call ("testing.tRunner")
// and then grabs the very next entry.
func suiteAnchor() (testName, location string, err error) {
callers := runtime.Callers(0, callStack)
for y := callers; y > 0; y-- {
callerId, file, conveyLine, found := runtime.Caller(y)
if !found {
continue
}
if name := runtime.FuncForPC(callerId).Name(); name != goTestHarness {
continue
}
callerId, file, conveyLine, _ = runtime.Caller(y - 1)
testName = runtime.FuncForPC(callerId).Name()
location = fmt.Sprintf("%s:%d", file, conveyLine)
return
}
return "", "", errors.New("Can't resolve test method name! Are you calling Convey() from a `*_test.go` file and a `Test*` method (because you should be)?")
}
// correlate links the current stack with the appropriate
// top-level Convey by comparing line numbers in its own stack trace
// with the registered file:line combo. It's come to this.
func correlate(locations map[string]string) (testName string) {
file, line := resolveTestFileAndLine()
closest := -1
for location, registeredTestName := range locations {
locationFile, rawLocationLine := splitFileAndLine(location)
if locationFile != file {
continue
}
locationLine, err := strconv.Atoi(rawLocationLine)
if err != nil || locationLine < line {
continue
}
if closest == -1 || locationLine < closest {
closest = locationLine
testName = registeredTestName
}
}
return
}
// splitFileAndLine receives a path and a line number in a single string,
// separated by a colon and splits them.
func splitFileAndLine(value string) (file, line string) {
parts := strings.Split(value, ":")
if len(parts) == 2 {
file = parts[0]
line = parts[1]
} else if len(parts) > 2 {
// 'C:/blah.go:123' (windows drive letter has two colons
// '-:--------:---' instead of just one to separate file and line)
file = strings.Join(parts[:2], ":")
line = parts[2]
}
return
}
// resolveTestFileAndLine is used as a last-ditch effort to correlate an
// assertion with the right executor and runner.
func resolveTestFileAndLine() (file string, line int) {
callers := runtime.Callers(0, callStack)
var found bool
for y := callers; y > 0; y-- {
_, file, line, found = runtime.Caller(y)
if !found {
continue
}
if strings.HasSuffix(file, "_test.go") {
return
}
}
return "", 0
}
const maxStackDepth = 100 // This had better be enough...
const goTestHarness = "testing.tRunner" // I hope this doesn't change...
var callStack []uintptr = make([]uintptr, maxStackDepth, maxStackDepth)
package convey
func discover(items []interface{}) *registration {
name, items := parseName(items)
test, items := parseGoTest(items)
action, items := parseAction(items)
if len(items) != 0 {
panic(parseError)
}
return newRegistration(name, action, test)
}
func item(items []interface{}) interface{} {
if len(items) == 0 {
panic(parseError)
}
return items[0]
}
func parseName(items []interface{}) (string, []interface{}) {
if name, parsed := item(items).(string); parsed {
return name, items[1:]
}
panic(parseError)
}
func parseGoTest(items []interface{}) (t, []interface{}) {
if test, parsed := item(items).(t); parsed {
return test, items[1:]
}
return nil, items
}
func parseFailureMode(items []interface{}) (FailureMode, []interface{}) {
if mode, parsed := item(items).(FailureMode); parsed {
return mode, items[1:]
}
return FailureInherits, items
}
func parseAction(items []interface{}) (*action, []interface{}) {
failure, items := parseFailureMode(items)
if action, parsed := item(items).(func()); parsed {
return newAction(action, failure), items[1:]
}
if item(items) == nil {
return newSkippedAction(skipReport, failure), items[1:]
}
panic(parseError)
}
// This interface allows us to pass the *testing.T struct
// throughout the internals of this tool without ever
// having to import the "testing" package.
type t interface {
Fail()
}
const parseError = "You must provide a name (string), then a *testing.T (if in outermost scope), an optional FailureMode, and then an action (func())."
// Package convey contains all of the public-facing entry points to this project.
// This means that it should never be required of the user to import any other
// packages from this project as they serve internal purposes.
package convey
import (
"fmt"
"github.com/smartystreets/goconvey/convey/reporting"
)
////////////////////////////////// Registration //////////////////////////////////
// Convey is the method intended for use when declaring the scopes
// of a specification. Each scope has a description and a func()
// which may contain other calls to Convey(), Reset() or Should-style
// assertions. Convey calls can be nested as far as you see fit.
//
// IMPORTANT NOTE: The top-level Convey() within a Test method
// must conform to the following signature:
//
// Convey(description string, t *testing.T, action func())
//
// All other calls should like like this (no need to pass in *testing.T):
//
// Convey(description string, action func())
//
// Don't worry, the goconvey will panic if you get it wrong so you can fix it.
//
// All Convey()-blocks also take an optional parameter of FailureMode which
// sets how goconvey should treat failures for So()-assertions in the block and
// nested blocks. See the constants in this file for the available options.
//
// By default it will inherit from its parent block and the top-level blocks
// start with setting of FailureHalts.
//
// This parameter is inserted before the block itself:
//
// Convey(description string, t *testing.T, mode FailureMode, action func())
// Convey(description string, mode FailureMode, action func())
//
// See the examples package for, well, examples.
func Convey(items ...interface{}) {
register(discover(items))
}
// SkipConvey is analagous to Convey except that the scope is not executed
// (which means that child scopes defined within this scope are not run either).
// The reporter will be notified that this step was skipped.
func SkipConvey(items ...interface{}) {
entry := discover(items)
entry.action = newSkippedAction(skipReport, entry.action.failureMode)
register(entry)
}
// FocusConvey is has the inverse effect of SkipConvey. If the top-level
// Convey is changed to `FocusConvey`, only nested scopes that are defined
// with FocusConvey will be run. The rest will be ignored completely. This
// is handy when debugging a large suite that runs a misbehaving function
// repeatedly as you can disable all but one of that function
// without swaths of `SkipConvey` calls, just a targeted chain of calls
// to FocusConvey.
func FocusConvey(items ...interface{}) {
entry := discover(items)
entry.Focus = true
register(entry)
}
func register(entry *registration) {
if entry.ShouldBeTopLevel() {
suites.Run(entry)
} else {
suites.Current().Register(entry)
}
}
func skipReport() {
suites.Current().Report(reporting.NewSkipReport())
}
// Reset registers a cleanup function to be run after each Convey()
// in the same scope. See the examples package for a simple use case.
func Reset(action func()) {
/* TODO: Failure mode configuration */
suites.Current().RegisterReset(newAction(action, FailureInherits))
}
/////////////////////////////////// Assertions ///////////////////////////////////
// assertion is an alias for a function with a signature that the convey.So()
// method can handle. Any future or custom assertions should conform to this
// method signature. The return value should be an empty string if the assertion
// passes and a well-formed failure message if not.
type assertion func(actual interface{}, expected ...interface{}) string
const assertionSuccess = ""
// So is the means by which assertions are made against the system under test.
// The majority of exported names in the assertions package begin with the word
// 'Should' and describe how the first argument (actual) should compare with any
// of the final (expected) arguments. How many final arguments are accepted
// depends on the particular assertion that is passed in as the assert argument.
// See the examples package for use cases and the assertions package for
// documentation on specific assertion methods.
func So(actual interface{}, assert assertion, expected ...interface{}) {
if result := assert(actual, expected...); result == assertionSuccess {
suites.Current().Report(reporting.NewSuccessReport())
} else {
suites.Current().Report(reporting.NewFailureReport(result))
}
}
// SkipSo is analagous to So except that the assertion that would have been passed
// to So is not executed and the reporter is notified that the assertion was skipped.
func SkipSo(stuff ...interface{}) {
skipReport()
}
// FailureMode is a type which determines how the So() blocks should fail
// if their assertion fails. See constants further down for acceptable values
type FailureMode string
const (
// FailureContinues is a failure mode which prevents failing
// So()-assertions from halting Convey-block execution, instead
// allowing the test to continue past failing So()-assertions.
FailureContinues FailureMode = "continue"
// FailureHalts is the default setting for a top-level Convey()-block
// and will cause all failing So()-assertions to halt further execution
// in that test-arm and continue on to the next arm.
FailureHalts FailureMode = "halt"
// FailureInherits is the default setting for failure-mode, it will
// default to the failure-mode of the parent block. You should never
// need to specify this mode in your tests..
FailureInherits FailureMode = "inherits"
)
var defaultFailureMode FailureMode = FailureHalts
// SetDefaultFailureMode allows you to specify the default failure mode
// for all Convey blocks. It is meant to be used in an init function to
// allow the default mode to be changd across all tests for an entire packgae
// but it can be used anywhere.
func SetDefaultFailureMode(mode FailureMode) {
if mode == FailureContinues || mode == FailureHalts {
defaultFailureMode = mode
} else {
panic("You may only use the constants named 'FailureContinues' and 'FailureHalts' as default failure modes.")
}
}
//////////////////////////////////// Print functions ////////////////////////////////////
// Print is analogous to fmt.Print (and it even calls fmt.Print). It ensures that
// output is aligned with the corresponding scopes in the web UI.
func Print(items ...interface{}) (written int, err error) {
fmt.Fprint(suites.Current(), items...)
return fmt.Print(items...)
}
// Print is analogous to fmt.Println (and it even calls fmt.Println). It ensures that
// output is aligned with the corresponding scopes in the web UI.
func Println(items ...interface{}) (written int, err error) {
fmt.Fprintln(suites.Current(), items...)
return fmt.Println(items...)
}
// Print is analogous to fmt.Printf (and it even calls fmt.Printf). It ensures that
// output is aligned with the corresponding scopes in the web UI.
func Printf(format string, items ...interface{}) (written int, err error) {
fmt.Fprintf(suites.Current(), format, items...)
return fmt.Printf(format, items...)
}
package convey
import "testing"
func TestFocusOnlyAtTopLevel(t *testing.T) {
output := prepare()
FocusConvey("hi", t, func() {
output += "done"
})
expectEqual(t, "done", output)
}
func TestFocus(t *testing.T) {
output := prepare()
FocusConvey("hi", t, func() {
output += "1"
Convey("bye", func() {
output += "2"
})
})
expectEqual(t, "1", output)
}
func TestNestedFocus(t *testing.T) {
output := prepare()
FocusConvey("hi", t, func() {
output += "1"
Convey("This shouldn't run", func() {
output += "boink!"
})
FocusConvey("This should run", func() {
output += "2"
FocusConvey("The should run too", func() {
output += "3"
})
Convey("The should NOT run", func() {
output += "blah blah blah!"
})
})
})
expectEqual(t, "123", output)
}
func TestForgotTopLevelFocus(t *testing.T) {
output := prepare()
Convey("1", t, func() {
output += "1"
FocusConvey("This will be run because the top-level lacks Focus", func() {
output += "2"
})
Convey("3", func() {
output += "3"
})
})
expectEqual(t, "1213", output)
}
// Package gotest contains internal functionality. Although this package
// contains one or more exported names it is not intended for public
// consumption. See the examples package for how to use this project.
package gotest
import (
"fmt"
"runtime"
"strings"
)
func FormatExternalFileAndLine() string {
file, line, _ := ResolveExternalCaller()
if line == -1 {
return "<unknown caller!>" // panic?
}
return fmt.Sprintf("%s:%d", file, line)
}
func ResolveExternalCaller() (file string, line int, name string) {
var caller_id uintptr
callers := runtime.Callers(0, callStack)
for x := 0; x < callers; x++ {
caller_id, file, line, _ = runtime.Caller(x)
if strings.HasSuffix(file, "_test.go") {
name = runtime.FuncForPC(caller_id).Name()
return
}
}
file, line, name = "<unkown file>", -1, "<unknown name>"
return // panic?
}
const maxStackDepth = 100 // This had better be enough...
var callStack []uintptr = make([]uintptr, maxStackDepth, maxStackDepth)
package convey
import (
"flag"
"os"
"github.com/smartystreets/goconvey/convey/reporting"
)
func init() {
declareFlags()
suites = newSuiteContext()
}
func declareFlags() {
flag.BoolVar(&json, "json", false, "When true, emits results in JSON blocks. Default: 'false'")
flag.BoolVar(&silent, "silent", false, "When true, all output from GoConvey is suppressed.")
flag.BoolVar(&story, "story", false, "When true, emits story output, otherwise emits dot output. When not provided, this flag mirros the value of the '-test.v' flag")
if noStoryFlagProvided() {
story = verboseEnabled
}
// FYI: flag.Parse() is called from the testing package.
}
func noStoryFlagProvided() bool {
return !story && !storyDisabled
}
func buildReporter() reporting.Reporter {
switch {
case testReporter != nil:
return testReporter
case json:
return reporting.BuildJsonReporter()
case silent:
return reporting.BuildSilentReporter()
case story:
return reporting.BuildStoryReporter()
default:
return reporting.BuildDotReporter()
}
}
var (
suites *suiteContext
// only set by internal tests
testReporter reporting.Reporter
)
var (
json bool
silent bool
story bool
verboseEnabled = flagFound("-test.v=true")
storyDisabled = flagFound("-story=false")
)
// flagFound parses the command line args manually for flags defined in other
// packages. Like the '-v' flag from the "testing" package, for instance.
func flagFound(flagValue string) bool {
for _, arg := range os.Args {
if arg == flagValue {
return true
}
}
return false
}
package convey
import (
"reflect"
"runtime"
"github.com/smartystreets/goconvey/convey/gotest"
)
type registration struct {
Situation string
action *action
Test t
File string
Line int
Focus bool
}
func (self *registration) ShouldBeTopLevel() bool {
return self.Test != nil
}
func newRegistration(situation string, action *action, test t) *registration {
file, line, _ := gotest.ResolveExternalCaller()
return &registration{
Situation: situation,
action: action,
Test: test,
File: file,
Line: line,
}
}
////////////////////////// action ///////////////////////
type action struct {
wrapped func()
name string
failureMode FailureMode
}
func (self *action) Invoke() {
self.wrapped()
}
func newAction(wrapped func(), mode FailureMode) *action {
return &action{
name: functionName(wrapped),
wrapped: wrapped,
failureMode: mode,
}
}
func newSkippedAction(wrapped func(), mode FailureMode) *action {
// The choice to use the filename and line number as the action name
// reflects the need for something unique but also that corresponds
// in a determinist way to the action itself.
return &action{
name: gotest.FormatExternalFileAndLine(),
wrapped: wrapped,
failureMode: mode,
}
}
///////////////////////// helpers //////////////////////////////
func functionName(action func()) string {
return runtime.FuncForPC(functionId(action)).Name()
}
func functionId(action func()) uintptr {
return reflect.ValueOf(action).Pointer()
}
package reporting
import (
"fmt"
"io"
)
type console struct{}
func (self *console) Write(p []byte) (n int, err error) {
return fmt.Print(string(p))
}
func NewConsole() io.Writer {
return new(console)
}
// Package reporting contains internal functionality related
// to console reporting and output. Although this package has
// exported names is not intended for public consumption. See the
// examples package for how to use this project.
package reporting
package reporting
import "fmt"
type dot struct{ out *Printer }
func (self *dot) BeginStory(story *StoryReport) {}
func (self *dot) Enter(scope *ScopeReport) {}
func (self *dot) Report(report *AssertionResult) {
if report.Error != nil {
fmt.Print(redColor)
self.out.Insert(dotError)
} else if report.Failure != "" {
fmt.Print(yellowColor)
self.out.Insert(dotFailure)
} else if report.Skipped {
fmt.Print(yellowColor)
self.out.Insert(dotSkip)
} else {
fmt.Print(greenColor)
self.out.Insert(dotSuccess)
}
fmt.Print(resetColor)
}
func (self *dot) Exit() {}
func (self *dot) EndStory() {}
func (self *dot) Write(content []byte) (written int, err error) {
return len(content), nil // no-op
}
func NewDotReporter(out *Printer) *dot {
self := new(dot)
self.out = out
return self
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment