Added missing mysql driver package dependency

This commit is contained in:
Torkel Ödegaard 2015-01-08 09:01:39 +01:00
parent 3226a3a58e
commit 18ff1569b9
83 changed files with 6993 additions and 5664 deletions

15
Godeps/Godeps.json generated
View File

@ -19,6 +19,11 @@
"Comment": "1.2.0-38-g9908e96",
"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"
}
]
}

View File

@ -0,0 +1,8 @@
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
Icon?
ehthumbs.db
Thumbs.db

View File

@ -0,0 +1,9 @@
language: go
go:
- 1.1
- 1.2
- 1.3
- tip
before_script:
- mysql -e 'create database gotest;'

View File

@ -0,0 +1,36 @@
# 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.

View File

@ -0,0 +1,83 @@
## 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

View File

@ -0,0 +1,40 @@
# 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.

View File

@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
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/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

View File

@ -0,0 +1,346 @@
# Go-MySQL-Driver
A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) package
![Go-MySQL-Driver logo](https://raw.github.com/wiki/go-sql-driver/mysql/gomysql_m.png "Golang Gopher holding the MySQL Dolphin")
**Latest stable Release:** [Version 1.2 (June 03, 2014)](https://github.com/go-sql-driver/mysql/releases)
[![Build Status](https://travis-ci.org/go-sql-driver/mysql.png?branch=master)](https://travis-ci.org/go-sql-driver/mysql)
---------------------------------------
* [Features](#features)
* [Requirements](#requirements)
* [Installation](#installation)
* [Usage](#usage)
* [DSN (Data Source Name)](#dsn-data-source-name)
* [Password](#password)
* [Protocol](#protocol)
* [Address](#address)
* [Parameters](#parameters)
* [Examples](#examples)
* [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support)
* [time.Time support](#timetime-support)
* [Unicode support](#unicode-support)
* [Testing / Development](#testing--development)
* [License](#license)
---------------------------------------
## Features
* Lightweight and [fast](https://github.com/go-sql-driver/sql-benchmark "golang MySQL-Driver performance")
* Native Go implementation. No C-bindings, just pure Go
* Connections over TCP/IPv4, TCP/IPv6 or Unix domain sockets
* Automatic handling of broken connections
* Automatic Connection Pooling *(by database/sql package)*
* Supports queries larger than 16MB
* Full [`sql.RawBytes`](http://golang.org/pkg/database/sql/#RawBytes) support.
* Intelligent `LONG DATA` handling in prepared statements
* Secure `LOAD DATA LOCAL INFILE` support with file Whitelisting and `io.Reader` support
* Optional `time.Time` parsing
## Requirements
* Go 1.1 or higher
* MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
---------------------------------------
## Installation
Simple install the package to your [$GOPATH](http://code.google.com/p/go-wiki/wiki/GOPATH "GOPATH") with the [go tool](http://golang.org/cmd/go/ "go command") from shell:
```bash
$ go get github.com/go-sql-driver/mysql
```
Make sure [Git is installed](http://git-scm.com/downloads) on your machine and in your system's `PATH`.
## Usage
_Go MySQL Driver_ is an implementation of Go's `database/sql/driver` interface. You only need to import the driver and can use the full [`database/sql`](http://golang.org/pkg/database/sql) API then.
Use `mysql` as `driverName` and a valid [DSN](#dsn-data-source-name) as `dataSourceName`:
```go
import "database/sql"
import _ "github.com/go-sql-driver/mysql"
db, err := sql.Open("mysql", "user:password@/dbname")
```
[Examples are available in our Wiki](https://github.com/go-sql-driver/mysql/wiki/Examples "Go-MySQL-Driver Examples").
### DSN (Data Source Name)
The Data Source Name has a common format, like e.g. [PEAR DB](http://pear.php.net/manual/en/package.database.db.intro-dsn.php) uses it, but without type-prefix (optional parts marked by squared brackets):
```
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]
```
A DSN in its fullest form:
```
username:password@protocol(address)/dbname?param=value
```
Except for the databasename, all values are optional. So the minimal DSN is:
```
/dbname
```
If you do not want to preselect a database, leave `dbname` empty:
```
/
```
This has the same effect as an empty DSN string:
```
```
#### Password
Passwords can consist of any character. Escaping is **not** necessary.
#### Protocol
See [net.Dial](http://golang.org/pkg/net/#Dial) for more information which networks are available.
In general you should use an Unix domain socket if available and TCP otherwise for best performance.
#### Address
For TCP and UDP networks, addresses have the form `host:port`.
If `host` is a literal IPv6 address, it must be enclosed in square brackets.
The functions [net.JoinHostPort](http://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](http://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form.
For Unix domain sockets the address is the absolute path to the MySQL-Server-socket, e.g. `/var/run/mysqld/mysqld.sock` or `/tmp/mysql.sock`.
#### Parameters
*Parameters are case-sensitive!*
Notice that any of `true`, `TRUE`, `True` or `1` is accepted to stand for a true boolean value. Not surprisingly, false can be specified as any of: `false`, `FALSE`, `False` or `0`.
##### `allowAllFiles`
```
Type: bool
Valid Values: true, false
Default: false
```
`allowAllFiles=true` disables the file Whitelist for `LOAD DATA LOCAL INFILE` and allows *all* files.
[*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)
##### `allowOldPasswords`
```
Type: bool
Valid Values: true, false
Default: false
```
`allowOldPasswords=true` allows the usage of the insecure old password method. This should be avoided, but is necessary in some cases. See also [the old_passwords wiki page](https://github.com/go-sql-driver/mysql/wiki/old_passwords).
##### `charset`
```
Type: string
Valid Values: <name>
Default: none
```
Sets the charset used for client-server interaction (`"SET NAMES <value>"`). If multiple charsets are set (separated by a comma), the following charset is used if setting the charset failes. This enables for example support for `utf8mb4` ([introduced in MySQL 5.5.3](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)) with fallback to `utf8` for older servers (`charset=utf8mb4,utf8`).
Usage of the `charset` parameter is discouraged because it issues additional queries to the server.
Unless you need the fallback behavior, please use `collation` instead.
##### `collation`
```
Type: string
Valid Values: <name>
Default: utf8_general_ci
```
Sets the collation used for client-server interaction on connection. In contrast to `charset`, `collation` does not issue additional queries. If the specified collation is unavailable on the target server, the connection will fail.
A list of valid charsets for a server is retrievable with `SHOW COLLATION`.
##### `clientFoundRows`
```
Type: bool
Valid Values: true, false
Default: false
```
`clientFoundRows=true` causes an UPDATE to return the number of matching rows instead of the number of rows changed.
##### `loc`
```
Type: string
Valid Values: <escaped name>
Default: UTC
```
Sets the location for time.Time values (when using `parseTime=true`). *"Local"* sets the system's location. See [time.LoadLocation](http://golang.org/pkg/time/#LoadLocation) for details.
Please keep in mind, that param values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`.
##### `parseTime`
```
Type: bool
Valid Values: true, false
Default: false
```
`parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string`
##### `strict`
```
Type: bool
Valid Values: true, false
Default: false
```
`strict=true` enables the strict mode in which MySQL warnings are treated as errors.
By default MySQL also treats notes as warnings. Use [`sql_notes=false`](http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_sql_notes) to ignore notes. See the [examples](#examples) for an DSN example.
##### `timeout`
```
Type: decimal number
Default: OS default
```
*Driver* side connection timeout. The value must be a string of decimal numbers, each with optional fraction and a unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. To set a server side timeout, use the parameter [`wait_timeout`](http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_wait_timeout).
##### `tls`
```
Type: bool / string
Valid Values: true, false, skip-verify, <name>
Default: false
```
`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](http://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
##### System Variables
All other parameters are interpreted as system variables:
* `autocommit`: `"SET autocommit=<value>"`
* `time_zone`: `"SET time_zone=<value>"`
* [`tx_isolation`](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation): `"SET tx_isolation=<value>"`
* `param`: `"SET <param>=<value>"`
*The values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!*
#### Examples
```
user@unix(/path/to/socket)/dbname
```
```
root:pw@unix(/tmp/mysql.sock)/myDatabase?loc=Local
```
```
user:password@tcp(localhost:5555)/dbname?tls=skip-verify&autocommit=true
```
Use the [strict mode](#strict) but ignore notes:
```
user:password@/dbname?strict=true&sql_notes=false
```
TCP via IPv6:
```
user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?timeout=90s&collation=utf8mb4_unicode_ci
```
TCP on a remote host, e.g. Amazon RDS:
```
id:password@tcp(your-amazonaws-uri.com:3306)/dbname
```
Google Cloud SQL on App Engine:
```
user@cloudsql(project-id:instance-name)/dbname
```
TCP using default port (3306) on localhost:
```
user:password@tcp/dbname?charset=utf8mb4,utf8&sys_var=esc%40ped
```
Use the default protocol (tcp) and host (localhost:3306):
```
user:password@/dbname
```
No Database preselected:
```
user:password@/
```
### `LOAD DATA LOCAL INFILE` support
For this feature you need direct access to the package. Therefore you must change the import path (no `_`):
```go
import "github.com/go-sql-driver/mysql"
```
Files must be whitelisted by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the Whitelist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)).
To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::<name>` then.
See the [godoc of Go-MySQL-Driver](http://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details.
### `time.Time` support
The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your programm.
However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical opposite in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](http://golang.org/pkg/time/#Location) with the `loc` DSN parameter.
**Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes).
Alternatively you can use the [`NullTime`](http://godoc.org/github.com/go-sql-driver/mysql#NullTime) type as the scan destination, which works with both `time.Time` and `string` / `[]byte`.
### Unicode support
Since version 1.1 Go-MySQL-Driver automatically uses the collation `utf8_general_ci` by default.
Other collations / charsets can be set using the [`collation`](#collation) DSN parameter.
Version 1.0 of the driver recommended adding `&charset=utf8` (alias for `SET NAMES utf8`) to the DSN to enable proper UTF-8 support. This is not necessary anymore. The [`collation`](#collation) parameter should be preferred to set another collation / charset than the default.
See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support.
## Testing / Development
To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details.
Go-MySQL-Driver is not feature-complete yet. Your help is very appreciated.
If you want to contribute, you can work on an [open issue](https://github.com/go-sql-driver/mysql/issues?state=open) or review a [pull request](https://github.com/go-sql-driver/mysql/pulls).
See the [Contribution Guidelines](https://github.com/go-sql-driver/mysql/blob/master/CONTRIBUTING.md) for details.
---------------------------------------
## License
Go-MySQL-Driver is licensed under the [Mozilla Public License Version 2.0](https://raw.github.com/go-sql-driver/mysql/master/LICENSE)
Mozilla summarizes the license scope as follows:
> MPL: The copyleft applies to any files containing MPLed code.
That means:
* You can **use** the **unchanged** source code both in private and commercially
* When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0)
* You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged**
Please read the [MPL 2.0 FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html) if you have further questions regarding the license.
You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE)
![Go Gopher and MySQL Dolphin](https://raw.github.com/wiki/go-sql-driver/mysql/go-mysql-driver_m.jpg "Golang Gopher transporting the MySQL Dolphin in a wheelbarrow")

View File

@ -0,0 +1,19 @@
// 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)
}

View File

@ -0,0 +1,208 @@
// 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()
}
}

View File

@ -0,0 +1,136 @@
// 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
}

View File

@ -0,0 +1,236 @@
// 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,
}

View File

@ -0,0 +1,268 @@
// 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
}

View File

@ -0,0 +1,132 @@
// 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
)

View File

@ -0,0 +1,138 @@
// 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{})
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,129 @@
// 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
}
}
}

View File

@ -0,0 +1,42 @@
// 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")
})
}

View File

@ -0,0 +1,162 @@
// 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
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
// 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
}

View File

@ -0,0 +1,101 @@
// 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
}

View File

@ -0,0 +1,112 @@
// 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
}

View File

@ -0,0 +1,31 @@
// 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
}

View File

@ -0,0 +1,785 @@
// 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/sha1"
"crypto/tls"
"database/sql/driver"
"encoding/binary"
"errors"
"fmt"
"io"
"net/url"
"strings"
"time"
)
var (
tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs
errInvalidDSNUnescaped = errors.New("Invalid DSN: Did you forget to escape a param value?")
errInvalidDSNAddr = errors.New("Invalid DSN: Network Address not terminated (missing closing brace)")
errInvalidDSNNoSlash = errors.New("Invalid DSN: Missing the slash separating the database name")
)
func init() {
tlsConfigRegister = make(map[string]*tls.Config)
}
// RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
// Use the key as a value in the DSN where tls=value.
//
// rootCertPool := x509.NewCertPool()
// pem, err := ioutil.ReadFile("/path/ca-cert.pem")
// if err != nil {
// log.Fatal(err)
// }
// if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
// log.Fatal("Failed to append PEM.")
// }
// clientCert := make([]tls.Certificate, 0, 1)
// certs, err := tls.LoadX509KeyPair("/path/client-cert.pem", "/path/client-key.pem")
// if err != nil {
// log.Fatal(err)
// }
// clientCert = append(clientCert, certs)
// mysql.RegisterTLSConfig("custom", &tls.Config{
// RootCAs: rootCertPool,
// Certificates: clientCert,
// })
// db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
//
func RegisterTLSConfig(key string, config *tls.Config) error {
if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" {
return fmt.Errorf("Key '%s' is reserved", key)
}
tlsConfigRegister[key] = config
return nil
}
// DeregisterTLSConfig removes the tls.Config associated with key.
func DeregisterTLSConfig(key string) {
delete(tlsConfigRegister, key)
}
// parseDSN parses the DSN string to a config
func parseDSN(dsn string) (cfg *config, err error) {
// New config with some default values
cfg = &config{
loc: time.UTC,
collation: defaultCollation,
}
// TODO: use strings.IndexByte when we can depend on Go 1.2
// [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
// Find the last '/' (since the password or the net addr might contain a '/')
foundSlash := false
for i := len(dsn) - 1; i >= 0; i-- {
if dsn[i] == '/' {
foundSlash = true
var j, k int
// left part is empty if i <= 0
if i > 0 {
// [username[:password]@][protocol[(address)]]
// Find the last '@' in dsn[:i]
for j = i; j >= 0; j-- {
if dsn[j] == '@' {
// username[:password]
// Find the first ':' in dsn[:j]
for k = 0; k < j; k++ {
if dsn[k] == ':' {
cfg.passwd = dsn[k+1 : j]
break
}
}
cfg.user = dsn[:k]
break
}
}
// [protocol[(address)]]
// Find the first '(' in dsn[j+1:i]
for k = j + 1; k < i; k++ {
if dsn[k] == '(' {
// dsn[i-1] must be == ')' if an address is specified
if dsn[i-1] != ')' {
if strings.ContainsRune(dsn[k+1:i], ')') {
return nil, errInvalidDSNUnescaped
}
return nil, errInvalidDSNAddr
}
cfg.addr = dsn[k+1 : i-1]
break
}
}
cfg.net = dsn[j+1 : k]
}
// dbname[?param1=value1&...&paramN=valueN]
// Find the first '?' in dsn[i+1:]
for j = i + 1; j < len(dsn); j++ {
if dsn[j] == '?' {
if err = parseDSNParams(cfg, dsn[j+1:]); err != nil {
return
}
break
}
}
cfg.dbname = dsn[i+1 : j]
break
}
}
if !foundSlash && len(dsn) > 0 {
return nil, errInvalidDSNNoSlash
}
// Set default network if empty
if cfg.net == "" {
cfg.net = "tcp"
}
// Set default address if empty
if cfg.addr == "" {
switch cfg.net {
case "tcp":
cfg.addr = "127.0.0.1:3306"
case "unix":
cfg.addr = "/tmp/mysql.sock"
default:
return nil, errors.New("Default addr for network '" + cfg.net + "' unknown")
}
}
return
}
// parseDSNParams parses the DSN "query string"
// Values must be url.QueryEscape'ed
func parseDSNParams(cfg *config, params string) (err error) {
for _, v := range strings.Split(params, "&") {
param := strings.SplitN(v, "=", 2)
if len(param) != 2 {
continue
}
// cfg params
switch value := param[1]; param[0] {
// Disable INFILE whitelist / enable all files
case "allowAllFiles":
var isBool bool
cfg.allowAllFiles, isBool = readBool(value)
if !isBool {
return fmt.Errorf("Invalid Bool value: %s", value)
}
// Use old authentication mode (pre MySQL 4.1)
case "allowOldPasswords":
var isBool bool
cfg.allowOldPasswords, isBool = readBool(value)
if !isBool {
return fmt.Errorf("Invalid Bool value: %s", value)
}
// Switch "rowsAffected" mode
case "clientFoundRows":
var isBool bool
cfg.clientFoundRows, isBool = readBool(value)
if !isBool {
return fmt.Errorf("Invalid Bool value: %s", value)
}
// Collation
case "collation":
collation, ok := collations[value]
if !ok {
// Note possibility for false negatives:
// could be triggered although the collation is valid if the
// collations map does not contain entries the server supports.
err = errors.New("unknown collation")
return
}
cfg.collation = collation
break
// Time Location
case "loc":
if value, err = url.QueryUnescape(value); err != nil {
return
}
cfg.loc, err = time.LoadLocation(value)
if err != nil {
return
}
// Dial Timeout
case "timeout":
cfg.timeout, err = time.ParseDuration(value)
if err != nil {
return
}
// TLS-Encryption
case "tls":
boolValue, isBool := readBool(value)
if isBool {
if boolValue {
cfg.tls = &tls.Config{}
}
} else {
if strings.ToLower(value) == "skip-verify" {
cfg.tls = &tls.Config{InsecureSkipVerify: true}
} else if tlsConfig, ok := tlsConfigRegister[value]; ok {
cfg.tls = tlsConfig
} else {
return fmt.Errorf("Invalid value / unknown config name: %s", value)
}
}
default:
// lazy init
if cfg.params == nil {
cfg.params = make(map[string]string)
}
if cfg.params[param[0]], err = url.QueryUnescape(value); err != nil {
return
}
}
}
return
}
// Returns the bool value of the input.
// The 2nd return value indicates if the input was a valid bool value
func readBool(input string) (value bool, valid bool) {
switch input {
case "1", "true", "TRUE", "True":
return true, true
case "0", "false", "FALSE", "False":
return false, true
}
// Not a valid bool value
return
}
/******************************************************************************
* Authentication *
******************************************************************************/
// Encrypt password using 4.1+ method
func scramblePassword(scramble, password []byte) []byte {
if len(password) == 0 {
return nil
}
// stage1Hash = SHA1(password)
crypt := sha1.New()
crypt.Write(password)
stage1 := crypt.Sum(nil)
// scrambleHash = SHA1(scramble + SHA1(stage1Hash))
// inner Hash
crypt.Reset()
crypt.Write(stage1)
hash := crypt.Sum(nil)
// outer Hash
crypt.Reset()
crypt.Write(scramble)
crypt.Write(hash)
scramble = crypt.Sum(nil)
// token = scrambleHash XOR stage1Hash
for i := range scramble {
scramble[i] ^= stage1[i]
}
return scramble
}
// Encrypt password using pre 4.1 (old password) method
// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
type myRnd struct {
seed1, seed2 uint32
}
const myRndMaxVal = 0x3FFFFFFF
// Pseudo random number generator
func newMyRnd(seed1, seed2 uint32) *myRnd {
return &myRnd{
seed1: seed1 % myRndMaxVal,
seed2: seed2 % myRndMaxVal,
}
}
// Tested to be equivalent to MariaDB's floating point variant
// http://play.golang.org/p/QHvhd4qved
// http://play.golang.org/p/RG0q4ElWDx
func (r *myRnd) NextByte() byte {
r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
return byte(uint64(r.seed1) * 31 / myRndMaxVal)
}
// Generate binary hash from byte string using insecure pre 4.1 method
func pwHash(password []byte) (result [2]uint32) {
var add uint32 = 7
var tmp uint32
result[0] = 1345345333
result[1] = 0x12345671
for _, c := range password {
// skip spaces and tabs in password
if c == ' ' || c == '\t' {
continue
}
tmp = uint32(c)
result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
result[1] += (result[1] << 8) ^ result[0]
add += tmp
}
// Remove sign bit (1<<31)-1)
result[0] &= 0x7FFFFFFF
result[1] &= 0x7FFFFFFF
return
}
// Encrypt password using insecure pre 4.1 method
func scrambleOldPassword(scramble, password []byte) []byte {
if len(password) == 0 {
return nil
}
scramble = scramble[:8]
hashPw := pwHash(password)
hashSc := pwHash(scramble)
r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
var out [8]byte
for i := range out {
out[i] = r.NextByte() + 64
}
mask := r.NextByte()
for i := range out {
out[i] ^= mask
}
return out[:]
}
/******************************************************************************
* Time related utils *
******************************************************************************/
// NullTime represents a time.Time that may be NULL.
// NullTime implements the Scanner interface so
// it can be used as a scan destination:
//
// var nt NullTime
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
// ...
// if nt.Valid {
// // use nt.Time
// } else {
// // NULL value
// }
//
// This NullTime implementation is not driver-specific
type NullTime struct {
Time time.Time
Valid bool // Valid is true if Time is not NULL
}
// Scan implements the Scanner interface.
// The value type must be time.Time or string / []byte (formatted time-string),
// otherwise Scan fails.
func (nt *NullTime) Scan(value interface{}) (err error) {
if value == nil {
nt.Time, nt.Valid = time.Time{}, false
return
}
switch v := value.(type) {
case time.Time:
nt.Time, nt.Valid = v, true
return
case []byte:
nt.Time, err = parseDateTime(string(v), time.UTC)
nt.Valid = (err == nil)
return
case string:
nt.Time, err = parseDateTime(v, time.UTC)
nt.Valid = (err == nil)
return
}
nt.Valid = false
return fmt.Errorf("Can't convert %T to time.Time", value)
}
// Value implements the driver Valuer interface.
func (nt NullTime) Value() (driver.Value, error) {
if !nt.Valid {
return nil, nil
}
return nt.Time, nil
}
func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
base := "0000-00-00 00:00:00.0000000"
switch len(str) {
case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
if str == base[:len(str)] {
return
}
t, err = time.Parse(timeFormat[:len(str)], str)
default:
err = fmt.Errorf("Invalid Time-String: %s", str)
return
}
// Adjust location
if err == nil && loc != time.UTC {
y, mo, d := t.Date()
h, mi, s := t.Clock()
t, err = time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc), nil
}
return
}
func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Value, error) {
switch num {
case 0:
return time.Time{}, nil
case 4:
return time.Date(
int(binary.LittleEndian.Uint16(data[:2])), // year
time.Month(data[2]), // month
int(data[3]), // day
0, 0, 0, 0,
loc,
), nil
case 7:
return time.Date(
int(binary.LittleEndian.Uint16(data[:2])), // year
time.Month(data[2]), // month
int(data[3]), // day
int(data[4]), // hour
int(data[5]), // minutes
int(data[6]), // seconds
0,
loc,
), nil
case 11:
return time.Date(
int(binary.LittleEndian.Uint16(data[:2])), // year
time.Month(data[2]), // month
int(data[3]), // day
int(data[4]), // hour
int(data[5]), // minutes
int(data[6]), // seconds
int(binary.LittleEndian.Uint32(data[7:11]))*1000, // nanoseconds
loc,
), nil
}
return nil, fmt.Errorf("Invalid DATETIME-packet length %d", num)
}
// zeroDateTime is used in formatBinaryDateTime to avoid an allocation
// if the DATE or DATETIME has the zero value.
// It must never be changed.
// The current behavior depends on database/sql copying the result.
var zeroDateTime = []byte("0000-00-00 00:00:00.000000")
func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value, error) {
// length expects the deterministic length of the zero value,
// negative time and 100+ hours are automatically added if needed
const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"
if len(src) == 0 {
if justTime {
return zeroDateTime[11 : 11+length], nil
}
return zeroDateTime[:length], nil
}
var dst []byte // return value
var pt, p1, p2, p3 byte // current digit pair
var zOffs byte // offset of value in zeroDateTime
if justTime {
switch length {
case
8, // time (can be up to 10 when negative and 100+ hours)
10, 11, 12, 13, 14, 15: // time with fractional seconds
default:
return nil, fmt.Errorf("illegal TIME length %d", length)
}
switch len(src) {
case 8, 12:
default:
return nil, fmt.Errorf("Invalid TIME-packet length %d", len(src))
}
// +2 to enable negative time and 100+ hours
dst = make([]byte, 0, length+2)
if src[0] == 1 {
dst = append(dst, '-')
}
if src[1] != 0 {
hour := uint16(src[1])*24 + uint16(src[5])
pt = byte(hour / 100)
p1 = byte(hour - 100*uint16(pt))
dst = append(dst, digits01[pt])
} else {
p1 = src[5]
}
zOffs = 11
src = src[6:]
} else {
switch length {
case 10, 19, 21, 22, 23, 24, 25, 26:
default:
t := "DATE"
if length > 10 {
t += "TIME"
}
return nil, fmt.Errorf("illegal %s length %d", t, length)
}
switch len(src) {
case 4, 7, 11:
default:
t := "DATE"
if length > 10 {
t += "TIME"
}
return nil, fmt.Errorf("illegal %s-packet length %d", t, len(src))
}
dst = make([]byte, 0, length)
// start with the date
year := binary.LittleEndian.Uint16(src[:2])
pt = byte(year / 100)
p1 = byte(year - 100*uint16(pt))
p2, p3 = src[2], src[3]
dst = append(dst,
digits10[pt], digits01[pt],
digits10[p1], digits01[p1], '-',
digits10[p2], digits01[p2], '-',
digits10[p3], digits01[p3],
)
if length == 10 {
return dst, nil
}
if len(src) == 4 {
return append(dst, zeroDateTime[10:length]...), nil
}
dst = append(dst, ' ')
p1 = src[4] // hour
src = src[5:]
}
// p1 is 2-digit hour, src is after hour
p2, p3 = src[0], src[1]
dst = append(dst,
digits10[p1], digits01[p1], ':',
digits10[p2], digits01[p2], ':',
digits10[p3], digits01[p3],
)
if length <= byte(len(dst)) {
return dst, nil
}
src = src[2:]
if len(src) == 0 {
return append(dst, zeroDateTime[19:zOffs+length]...), nil
}
microsecs := binary.LittleEndian.Uint32(src[:4])
p1 = byte(microsecs / 10000)
microsecs -= 10000 * uint32(p1)
p2 = byte(microsecs / 100)
microsecs -= 100 * uint32(p2)
p3 = byte(microsecs)
switch decimals := zOffs + length - 20; decimals {
default:
return append(dst, '.',
digits10[p1], digits01[p1],
digits10[p2], digits01[p2],
digits10[p3], digits01[p3],
), nil
case 1:
return append(dst, '.',
digits10[p1],
), nil
case 2:
return append(dst, '.',
digits10[p1], digits01[p1],
), nil
case 3:
return append(dst, '.',
digits10[p1], digits01[p1],
digits10[p2],
), nil
case 4:
return append(dst, '.',
digits10[p1], digits01[p1],
digits10[p2], digits01[p2],
), nil
case 5:
return append(dst, '.',
digits10[p1], digits01[p1],
digits10[p2], digits01[p2],
digits10[p3],
), nil
}
}
/******************************************************************************
* Convert from and to bytes *
******************************************************************************/
func uint64ToBytes(n uint64) []byte {
return []byte{
byte(n),
byte(n >> 8),
byte(n >> 16),
byte(n >> 24),
byte(n >> 32),
byte(n >> 40),
byte(n >> 48),
byte(n >> 56),
}
}
func uint64ToString(n uint64) []byte {
var a [20]byte
i := 20
// U+0030 = 0
// ...
// U+0039 = 9
var q uint64
for n >= 10 {
i--
q = n / 10
a[i] = uint8(n-q*10) + 0x30
n = q
}
i--
a[i] = uint8(n) + 0x30
return a[i:]
}
// treats string value as unsigned integer representation
func stringToInt(b []byte) int {
val := 0
for i := range b {
val *= 10
val += int(b[i] - 0x30)
}
return val
}
// returns the string read as a bytes slice, wheter the value is NULL,
// the number of bytes read and an error, in case the string is longer than
// the input slice
func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {
// Get length
num, isNull, n := readLengthEncodedInteger(b)
if num < 1 {
return b[n:n], isNull, n, nil
}
n += int(num)
// Check data length
if len(b) >= n {
return b[n-int(num) : n], false, n, nil
}
return nil, false, n, io.EOF
}
// returns the number of bytes skipped and an error, in case the string is
// longer than the input slice
func skipLengthEncodedString(b []byte) (int, error) {
// Get length
num, _, n := readLengthEncodedInteger(b)
if num < 1 {
return n, nil
}
n += int(num)
// Check data length
if len(b) >= n {
return n, nil
}
return n, io.EOF
}
// returns the number read, whether the value is NULL and the number of bytes read
func readLengthEncodedInteger(b []byte) (uint64, bool, int) {
switch b[0] {
// 251: NULL
case 0xfb:
return 0, true, 1
// 252: value of following 2
case 0xfc:
return uint64(b[1]) | uint64(b[2])<<8, false, 3
// 253: value of following 3
case 0xfd:
return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16, false, 4
// 254: value of following 8
case 0xfe:
return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 |
uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 |
uint64(b[7])<<48 | uint64(b[8])<<56,
false, 9
}
// 0-250: value of first byte
return uint64(b[0]), false, 1
}
// encodes a uint64 value and appends it to the given bytes slice
func appendLengthEncodedInteger(b []byte, n uint64) []byte {
switch {
case n <= 250:
return append(b, byte(n))
case n <= 0xffff:
return append(b, 0xfc, byte(n), byte(n>>8))
case n <= 0xffffff:
return append(b, 0xfd, byte(n), byte(n>>8), byte(n>>16))
}
return append(b, 0xfe, byte(n), byte(n>>8), byte(n>>16), byte(n>>24),
byte(n>>32), byte(n>>40), byte(n>>48), byte(n>>56))
}

View File

@ -0,0 +1,212 @@
// 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)
}

View File

@ -1,63 +0,0 @@
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
)

View File

@ -1,140 +0,0 @@
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)
}

View File

@ -1,103 +0,0 @@
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))
}

View File

@ -1,3 +0,0 @@
// Package assertions contains the implementations for all assertions which
// are referenced in the convey package for use with the So(...) method.
package assertions

View File

@ -1,281 +0,0 @@
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
}

View File

@ -1,256 +0,0 @@
package assertions
import (
"fmt"
"reflect"
"testing"
)
func TestShouldEqual(t *testing.T) {
serializer = newFakeSerializer()
fail(t, so(1, ShouldEqual), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so(1, ShouldEqual, 1, 2), "This assertion requires exactly 1 comparison values (you provided 2).")
fail(t, so(1, ShouldEqual, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).")
pass(t, so(1, ShouldEqual, 1))
fail(t, so(1, ShouldEqual, 2), "2|1|Expected: '2' Actual: '1' (Should be equal)")
pass(t, so(true, ShouldEqual, true))
fail(t, so(true, ShouldEqual, false), "false|true|Expected: 'false' Actual: 'true' (Should be equal)")
pass(t, so("hi", ShouldEqual, "hi"))
fail(t, so("hi", ShouldEqual, "bye"), "bye|hi|Expected: 'bye' Actual: 'hi' (Should be equal)")
pass(t, so(42, ShouldEqual, uint(42)))
fail(t, so(Thing1{"hi"}, ShouldEqual, Thing1{}), "{}|{hi}|Expected: '{}' Actual: '{hi}' (Should be equal)")
fail(t, so(Thing1{"hi"}, ShouldEqual, Thing1{"hi"}), "{hi}|{hi}|Expected: '{hi}' Actual: '{hi}' (Should be equal)")
fail(t, so(&Thing1{"hi"}, ShouldEqual, &Thing1{"hi"}), "&{hi}|&{hi}|Expected: '&{hi}' Actual: '&{hi}' (Should be equal)")
fail(t, so(Thing1{}, ShouldEqual, Thing2{}), "{}|{}|Expected: '{}' Actual: '{}' (Should be equal)")
}
func TestShouldNotEqual(t *testing.T) {
fail(t, so(1, ShouldNotEqual), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so(1, ShouldNotEqual, 1, 2), "This assertion requires exactly 1 comparison values (you provided 2).")
fail(t, so(1, ShouldNotEqual, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).")
pass(t, so(1, ShouldNotEqual, 2))
fail(t, so(1, ShouldNotEqual, 1), "Expected '1' to NOT equal '1' (but it did)!")
pass(t, so(true, ShouldNotEqual, false))
fail(t, so(true, ShouldNotEqual, true), "Expected 'true' to NOT equal 'true' (but it did)!")
pass(t, so("hi", ShouldNotEqual, "bye"))
fail(t, so("hi", ShouldNotEqual, "hi"), "Expected 'hi' to NOT equal 'hi' (but it did)!")
pass(t, so(&Thing1{"hi"}, ShouldNotEqual, &Thing1{"hi"}))
pass(t, so(Thing1{"hi"}, ShouldNotEqual, Thing1{"hi"}))
pass(t, so(Thing1{}, ShouldNotEqual, Thing1{}))
pass(t, so(Thing1{}, ShouldNotEqual, Thing2{}))
}
func TestShouldAlmostEqual(t *testing.T) {
fail(t, so(1, ShouldAlmostEqual), "This assertion requires exactly one comparison value and an optional delta (you provided neither)")
fail(t, so(1, ShouldAlmostEqual, 1, 2, 3), "This assertion requires exactly one comparison value and an optional delta (you provided more values)")
// with the default delta
pass(t, so(1, ShouldAlmostEqual, .99999999999999))
pass(t, so(1.3612499999999996, ShouldAlmostEqual, 1.36125))
pass(t, so(0.7285312499999999, ShouldAlmostEqual, 0.72853125))
fail(t, so(1, ShouldAlmostEqual, .99), "Expected '1' to almost equal '0.99' (but it didn't)!")
// with a different delta
pass(t, so(100.0, ShouldAlmostEqual, 110.0, 10.0))
fail(t, so(100.0, ShouldAlmostEqual, 111.0, 10.5), "Expected '100' to almost equal '111' (but it didn't)!")
// ints should work
pass(t, so(100, ShouldAlmostEqual, 100.0))
fail(t, so(100, ShouldAlmostEqual, 99.0), "Expected '100' to almost equal '99' (but it didn't)!")
// float32 should work
pass(t, so(float64(100.0), ShouldAlmostEqual, float32(100.0)))
fail(t, so(float32(100.0), ShouldAlmostEqual, 99.0, float32(0.1)), "Expected '100' to almost equal '99' (but it didn't)!")
}
func TestShouldNotAlmostEqual(t *testing.T) {
fail(t, so(1, ShouldNotAlmostEqual), "This assertion requires exactly one comparison value and an optional delta (you provided neither)")
fail(t, so(1, ShouldNotAlmostEqual, 1, 2, 3), "This assertion requires exactly one comparison value and an optional delta (you provided more values)")
// with the default delta
fail(t, so(1, ShouldNotAlmostEqual, .99999999999999), "Expected '1' to NOT almost equal '0.99999999999999' (but it did)!")
fail(t, so(1.3612499999999996, ShouldNotAlmostEqual, 1.36125), "Expected '1.3612499999999996' to NOT almost equal '1.36125' (but it did)!")
pass(t, so(1, ShouldNotAlmostEqual, .99))
// with a different delta
fail(t, so(100.0, ShouldNotAlmostEqual, 110.0, 10.0), "Expected '100' to NOT almost equal '110' (but it did)!")
pass(t, so(100.0, ShouldNotAlmostEqual, 111.0, 10.5))
// ints should work
fail(t, so(100, ShouldNotAlmostEqual, 100.0), "Expected '100' to NOT almost equal '100' (but it did)!")
pass(t, so(100, ShouldNotAlmostEqual, 99.0))
// float32 should work
fail(t, so(float64(100.0), ShouldNotAlmostEqual, float32(100.0)), "Expected '100' to NOT almost equal '100' (but it did)!")
pass(t, so(float32(100.0), ShouldNotAlmostEqual, 99.0, float32(0.1)))
}
func TestShouldResemble(t *testing.T) {
serializer = newFakeSerializer()
fail(t, so(Thing1{"hi"}, ShouldResemble), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so(Thing1{"hi"}, ShouldResemble, Thing1{"hi"}, Thing1{"hi"}), "This assertion requires exactly 1 comparison values (you provided 2).")
pass(t, so(Thing1{"hi"}, ShouldResemble, Thing1{"hi"}))
fail(t, so(Thing1{"hi"}, ShouldResemble, Thing1{"bye"}), "{bye}|{hi}|Expected: 'assertions.Thing1({a:bye})' Actual: 'assertions.Thing1({a:hi})' (Should resemble)!")
}
func TestShouldNotResemble(t *testing.T) {
fail(t, so(Thing1{"hi"}, ShouldNotResemble), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so(Thing1{"hi"}, ShouldNotResemble, Thing1{"hi"}, Thing1{"hi"}), "This assertion requires exactly 1 comparison values (you provided 2).")
pass(t, so(Thing1{"hi"}, ShouldNotResemble, Thing1{"bye"}))
fail(t, so(Thing1{"hi"}, ShouldNotResemble, Thing1{"hi"}),
"Expected 'assertions.Thing1({a:hi})' to NOT resemble 'assertions.Thing1({a:hi})' (but it did)!")
pass(t, so(map[string]string{"hi": "bye"}, ShouldResemble, map[string]string{"hi": "bye"}))
fail(t, so(StringStringMapAlias{"hi": "bye"}, ShouldResemble, map[string]string{"hi": "bye"}),
"map[hi:bye]|map[hi:bye]|Expected: 'map[string]string(map[hi:bye])' Actual: 'assertions.StringStringMapAlias(map[hi:bye])' (Should resemble)!")
pass(t, so(IntAlias(42), ShouldNotResemble, 42))
fail(t, so(IntAlias(42), ShouldResemble, 42), "42|42|Expected: 'int(42)' Actual: 'assertions.IntAlias(42)' (Should resemble)!")
fail(t, so(StringAlias("hi"), ShouldResemble, "hi"), "hi|hi|Expected: 'string(hi)' Actual: 'assertions.StringAlias(hi)' (Should resemble)!")
pass(t, so(StringSliceAlias{"hi", "bye"}, ShouldNotResemble, []string{"hi", "bye"}))
fail(t, so(StringSliceAlias{"hi", "bye"}, ShouldResemble, []string{"hi", "bye"}),
"[hi bye]|[hi bye]|Expected: '[]string([hi bye])' Actual: 'assertions.StringSliceAlias([hi bye])' (Should resemble)!")
}
func TestShouldPointTo(t *testing.T) {
serializer = newFakeSerializer()
t1 := &Thing1{}
t2 := t1
t3 := &Thing1{}
pointer1 := reflect.ValueOf(t1).Pointer()
pointer3 := reflect.ValueOf(t3).Pointer()
fail(t, so(t1, ShouldPointTo), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so(t1, ShouldPointTo, t2, t3), "This assertion requires exactly 1 comparison values (you provided 2).")
pass(t, so(t1, ShouldPointTo, t2))
fail(t, so(t1, ShouldPointTo, t3), fmt.Sprintf(
"%v|%v|Expected '&{a:}' (address: '%v') and '&{a:}' (address: '%v') to be the same address (but their weren't)!",
pointer3, pointer1, pointer1, pointer3))
t4 := Thing1{}
t5 := t4
fail(t, so(t4, ShouldPointTo, t5), "Both arguments should be pointers (the first was not)!")
fail(t, so(&t4, ShouldPointTo, t5), "Both arguments should be pointers (the second was not)!")
fail(t, so(nil, ShouldPointTo, nil), "Both arguments should be pointers (the first was nil)!")
fail(t, so(&t4, ShouldPointTo, nil), "Both arguments should be pointers (the second was nil)!")
}
func TestShouldNotPointTo(t *testing.T) {
t1 := &Thing1{}
t2 := t1
t3 := &Thing1{}
pointer1 := reflect.ValueOf(t1).Pointer()
fail(t, so(t1, ShouldNotPointTo), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so(t1, ShouldNotPointTo, t2, t3), "This assertion requires exactly 1 comparison values (you provided 2).")
pass(t, so(t1, ShouldNotPointTo, t3))
fail(t, so(t1, ShouldNotPointTo, t2), fmt.Sprintf("Expected '&{a:}' and '&{a:}' to be different references (but they matched: '%v')!", pointer1))
t4 := Thing1{}
t5 := t4
fail(t, so(t4, ShouldNotPointTo, t5), "Both arguments should be pointers (the first was not)!")
fail(t, so(&t4, ShouldNotPointTo, t5), "Both arguments should be pointers (the second was not)!")
fail(t, so(nil, ShouldNotPointTo, nil), "Both arguments should be pointers (the first was nil)!")
fail(t, so(&t4, ShouldNotPointTo, nil), "Both arguments should be pointers (the second was nil)!")
}
func TestShouldBeNil(t *testing.T) {
fail(t, so(nil, ShouldBeNil, nil, nil, nil), "This assertion requires exactly 0 comparison values (you provided 3).")
fail(t, so(nil, ShouldBeNil, nil), "This assertion requires exactly 0 comparison values (you provided 1).")
pass(t, so(nil, ShouldBeNil))
fail(t, so(1, ShouldBeNil), "Expected: nil Actual: '1'")
var thing Thinger
pass(t, so(thing, ShouldBeNil))
thing = &Thing{}
fail(t, so(thing, ShouldBeNil), "Expected: nil Actual: '&{}'")
var thingOne *Thing1
pass(t, so(thingOne, ShouldBeNil))
var nilSlice []int = nil
pass(t, so(nilSlice, ShouldBeNil))
var nilMap map[string]string = nil
pass(t, so(nilMap, ShouldBeNil))
var nilChannel chan int = nil
pass(t, so(nilChannel, ShouldBeNil))
var nilFunc func() = nil
pass(t, so(nilFunc, ShouldBeNil))
var nilInterface interface{} = nil
pass(t, so(nilInterface, ShouldBeNil))
}
func TestShouldNotBeNil(t *testing.T) {
fail(t, so(nil, ShouldNotBeNil, nil, nil, nil), "This assertion requires exactly 0 comparison values (you provided 3).")
fail(t, so(nil, ShouldNotBeNil, nil), "This assertion requires exactly 0 comparison values (you provided 1).")
fail(t, so(nil, ShouldNotBeNil), "Expected '<nil>' to NOT be nil (but it was)!")
pass(t, so(1, ShouldNotBeNil))
var thing Thinger
fail(t, so(thing, ShouldNotBeNil), "Expected '<nil>' to NOT be nil (but it was)!")
thing = &Thing{}
pass(t, so(thing, ShouldNotBeNil))
}
func TestShouldBeTrue(t *testing.T) {
fail(t, so(true, ShouldBeTrue, 1, 2, 3), "This assertion requires exactly 0 comparison values (you provided 3).")
fail(t, so(true, ShouldBeTrue, 1), "This assertion requires exactly 0 comparison values (you provided 1).")
fail(t, so(false, ShouldBeTrue), "Expected: true Actual: false")
fail(t, so(1, ShouldBeTrue), "Expected: true Actual: 1")
pass(t, so(true, ShouldBeTrue))
}
func TestShouldBeFalse(t *testing.T) {
fail(t, so(false, ShouldBeFalse, 1, 2, 3), "This assertion requires exactly 0 comparison values (you provided 3).")
fail(t, so(false, ShouldBeFalse, 1), "This assertion requires exactly 0 comparison values (you provided 1).")
fail(t, so(true, ShouldBeFalse), "Expected: false Actual: true")
fail(t, so(1, ShouldBeFalse), "Expected: false Actual: 1")
pass(t, so(false, ShouldBeFalse))
}
func TestShouldBeZeroValue(t *testing.T) {
serializer = newFakeSerializer()
fail(t, so(0, ShouldBeZeroValue, 1, 2, 3), "This assertion requires exactly 0 comparison values (you provided 3).")
fail(t, so(false, ShouldBeZeroValue, true), "This assertion requires exactly 0 comparison values (you provided 1).")
fail(t, so(1, ShouldBeZeroValue), "0|1|'1' should have been the zero value") //"Expected: (zero value) Actual: 1")
fail(t, so(true, ShouldBeZeroValue), "false|true|'true' should have been the zero value") //"Expected: (zero value) Actual: true")
fail(t, so("123", ShouldBeZeroValue), "|123|'123' should have been the zero value") //"Expected: (zero value) Actual: 123")
fail(t, so(" ", ShouldBeZeroValue), "| |' ' should have been the zero value") //"Expected: (zero value) Actual: ")
fail(t, so([]string{"Nonempty"}, ShouldBeZeroValue), "[]|[Nonempty]|'[Nonempty]' should have been the zero value") //"Expected: (zero value) Actual: [Nonempty]")
fail(t, so(struct{ a string }{a: "asdf"}, ShouldBeZeroValue), "{}|{asdf}|'{a:asdf}' should have been the zero value")
pass(t, so(0, ShouldBeZeroValue))
pass(t, so(false, ShouldBeZeroValue))
pass(t, so("", ShouldBeZeroValue))
pass(t, so(struct{}{}, ShouldBeZeroValue))
}

View File

@ -1,22 +0,0 @@
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
}

View File

@ -1,7 +0,0 @@
package assertions
var serializer Serializer
func init() {
serializer = newSerializer()
}

View File

@ -1,86 +0,0 @@
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!)"
)

View File

@ -1,115 +0,0 @@
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
}

View File

@ -1,53 +0,0 @@
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"))
}

View File

@ -1,141 +0,0 @@
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
}

View File

@ -1,145 +0,0 @@
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)!")
}

View File

@ -1,31 +0,0 @@
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{}
}

View File

@ -1,28 +0,0 @@
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)
}
}

View File

@ -1,183 +0,0 @@
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
}

View File

@ -1,102 +0,0 @@
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))
}

View File

@ -1,202 +0,0 @@
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 ""
}

View File

@ -1,159 +0,0 @@
package assertions
import (
"fmt"
"testing"
"time"
)
func TestShouldHappenBefore(t *testing.T) {
fail(t, so(0, ShouldHappenBefore), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so(0, ShouldHappenBefore, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).")
fail(t, so(0, ShouldHappenBefore, 1), shouldUseTimes)
fail(t, so(0, ShouldHappenBefore, time.Now()), shouldUseTimes)
fail(t, so(time.Now(), ShouldHappenBefore, 0), shouldUseTimes)
fail(t, so(january3, ShouldHappenBefore, january1), fmt.Sprintf("Expected '%s' to happen before '%s' (it happened '48h0m0s' after)!", pretty(january3), pretty(january1)))
fail(t, so(january3, ShouldHappenBefore, january3), fmt.Sprintf("Expected '%s' to happen before '%s' (it happened '0' after)!", pretty(january3), pretty(january3)))
pass(t, so(january1, ShouldHappenBefore, january3))
}
func TestShouldHappenOnOrBefore(t *testing.T) {
fail(t, so(0, ShouldHappenOnOrBefore), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so(0, ShouldHappenOnOrBefore, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).")
fail(t, so(0, ShouldHappenOnOrBefore, 1), shouldUseTimes)
fail(t, so(0, ShouldHappenOnOrBefore, time.Now()), shouldUseTimes)
fail(t, so(time.Now(), ShouldHappenOnOrBefore, 0), shouldUseTimes)
fail(t, so(january3, ShouldHappenOnOrBefore, january1), fmt.Sprintf("Expected '%s' to happen before '%s' (it happened '48h0m0s' after)!", pretty(january3), pretty(january1)))
pass(t, so(january3, ShouldHappenOnOrBefore, january3))
pass(t, so(january1, ShouldHappenOnOrBefore, january3))
}
func TestShouldHappenAfter(t *testing.T) {
fail(t, so(0, ShouldHappenAfter), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so(0, ShouldHappenAfter, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).")
fail(t, so(0, ShouldHappenAfter, 1), shouldUseTimes)
fail(t, so(0, ShouldHappenAfter, time.Now()), shouldUseTimes)
fail(t, so(time.Now(), ShouldHappenAfter, 0), shouldUseTimes)
fail(t, so(january1, ShouldHappenAfter, january2), fmt.Sprintf("Expected '%s' to happen after '%s' (it happened '24h0m0s' before)!", pretty(january1), pretty(january2)))
fail(t, so(january1, ShouldHappenAfter, january1), fmt.Sprintf("Expected '%s' to happen after '%s' (it happened '0' before)!", pretty(january1), pretty(january1)))
pass(t, so(january3, ShouldHappenAfter, january1))
}
func TestShouldHappenOnOrAfter(t *testing.T) {
fail(t, so(0, ShouldHappenOnOrAfter), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so(0, ShouldHappenOnOrAfter, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).")
fail(t, so(0, ShouldHappenOnOrAfter, 1), shouldUseTimes)
fail(t, so(0, ShouldHappenOnOrAfter, time.Now()), shouldUseTimes)
fail(t, so(time.Now(), ShouldHappenOnOrAfter, 0), shouldUseTimes)
fail(t, so(january1, ShouldHappenOnOrAfter, january2), fmt.Sprintf("Expected '%s' to happen after '%s' (it happened '24h0m0s' before)!", pretty(january1), pretty(january2)))
pass(t, so(january1, ShouldHappenOnOrAfter, january1))
pass(t, so(january3, ShouldHappenOnOrAfter, january1))
}
func TestShouldHappenBetween(t *testing.T) {
fail(t, so(0, ShouldHappenBetween), "This assertion requires exactly 2 comparison values (you provided 0).")
fail(t, so(0, ShouldHappenBetween, 1, 2, 3), "This assertion requires exactly 2 comparison values (you provided 3).")
fail(t, so(0, ShouldHappenBetween, 1, 2), shouldUseTimes)
fail(t, so(0, ShouldHappenBetween, time.Now(), time.Now()), shouldUseTimes)
fail(t, so(time.Now(), ShouldHappenBetween, 0, time.Now()), shouldUseTimes)
fail(t, so(time.Now(), ShouldHappenBetween, time.Now(), 9), shouldUseTimes)
fail(t, so(january1, ShouldHappenBetween, january2, january4), fmt.Sprintf("Expected '%s' to happen between '%s' and '%s' (it happened '24h0m0s' outside threshold)!", pretty(january1), pretty(january2), pretty(january4)))
fail(t, so(january2, ShouldHappenBetween, january2, january4), fmt.Sprintf("Expected '%s' to happen between '%s' and '%s' (it happened '0' outside threshold)!", pretty(january2), pretty(january2), pretty(january4)))
pass(t, so(january3, ShouldHappenBetween, january2, january4))
fail(t, so(january4, ShouldHappenBetween, january2, january4), fmt.Sprintf("Expected '%s' to happen between '%s' and '%s' (it happened '0' outside threshold)!", pretty(january4), pretty(january2), pretty(january4)))
fail(t, so(january5, ShouldHappenBetween, january2, january4), fmt.Sprintf("Expected '%s' to happen between '%s' and '%s' (it happened '24h0m0s' outside threshold)!", pretty(january5), pretty(january2), pretty(january4)))
}
func TestShouldHappenOnOrBetween(t *testing.T) {
fail(t, so(0, ShouldHappenOnOrBetween), "This assertion requires exactly 2 comparison values (you provided 0).")
fail(t, so(0, ShouldHappenOnOrBetween, 1, 2, 3), "This assertion requires exactly 2 comparison values (you provided 3).")
fail(t, so(0, ShouldHappenOnOrBetween, 1, time.Now()), shouldUseTimes)
fail(t, so(0, ShouldHappenOnOrBetween, time.Now(), 1), shouldUseTimes)
fail(t, so(time.Now(), ShouldHappenOnOrBetween, 0, 1), shouldUseTimes)
fail(t, so(january1, ShouldHappenOnOrBetween, january2, january4), fmt.Sprintf("Expected '%s' to happen between '%s' and '%s' (it happened '24h0m0s' outside threshold)!", pretty(january1), pretty(january2), pretty(january4)))
pass(t, so(january2, ShouldHappenOnOrBetween, january2, january4))
pass(t, so(january3, ShouldHappenOnOrBetween, january2, january4))
pass(t, so(january4, ShouldHappenOnOrBetween, january2, january4))
fail(t, so(january5, ShouldHappenOnOrBetween, january2, january4), fmt.Sprintf("Expected '%s' to happen between '%s' and '%s' (it happened '24h0m0s' outside threshold)!", pretty(january5), pretty(january2), pretty(january4)))
}
func TestShouldNotHappenOnOrBetween(t *testing.T) {
fail(t, so(0, ShouldNotHappenOnOrBetween), "This assertion requires exactly 2 comparison values (you provided 0).")
fail(t, so(0, ShouldNotHappenOnOrBetween, 1, 2, 3), "This assertion requires exactly 2 comparison values (you provided 3).")
fail(t, so(0, ShouldNotHappenOnOrBetween, 1, time.Now()), shouldUseTimes)
fail(t, so(0, ShouldNotHappenOnOrBetween, time.Now(), 1), shouldUseTimes)
fail(t, so(time.Now(), ShouldNotHappenOnOrBetween, 0, 1), shouldUseTimes)
pass(t, so(january1, ShouldNotHappenOnOrBetween, january2, january4))
fail(t, so(january2, ShouldNotHappenOnOrBetween, january2, january4), fmt.Sprintf("Expected '%s' to NOT happen on or between '%s' and '%s' (but it did)!", pretty(january2), pretty(january2), pretty(january4)))
fail(t, so(january3, ShouldNotHappenOnOrBetween, january2, january4), fmt.Sprintf("Expected '%s' to NOT happen on or between '%s' and '%s' (but it did)!", pretty(january3), pretty(january2), pretty(january4)))
fail(t, so(january4, ShouldNotHappenOnOrBetween, january2, january4), fmt.Sprintf("Expected '%s' to NOT happen on or between '%s' and '%s' (but it did)!", pretty(january4), pretty(january2), pretty(january4)))
pass(t, so(january5, ShouldNotHappenOnOrBetween, january2, january4))
}
func TestShouldHappenWithin(t *testing.T) {
fail(t, so(0, ShouldHappenWithin), "This assertion requires exactly 2 comparison values (you provided 0).")
fail(t, so(0, ShouldHappenWithin, 1, 2, 3), "This assertion requires exactly 2 comparison values (you provided 3).")
fail(t, so(0, ShouldHappenWithin, 1, 2), shouldUseDurationAndTime)
fail(t, so(0, ShouldHappenWithin, oneDay, time.Now()), shouldUseDurationAndTime)
fail(t, so(time.Now(), ShouldHappenWithin, 0, time.Now()), shouldUseDurationAndTime)
fail(t, so(january1, ShouldHappenWithin, oneDay, january3), fmt.Sprintf("Expected '%s' to happen between '%s' and '%s' (it happened '24h0m0s' outside threshold)!", pretty(january1), pretty(january2), pretty(january4)))
pass(t, so(january2, ShouldHappenWithin, oneDay, january3))
pass(t, so(january3, ShouldHappenWithin, oneDay, january3))
pass(t, so(january4, ShouldHappenWithin, oneDay, january3))
fail(t, so(january5, ShouldHappenWithin, oneDay, january3), fmt.Sprintf("Expected '%s' to happen between '%s' and '%s' (it happened '24h0m0s' outside threshold)!", pretty(january5), pretty(january2), pretty(january4)))
}
func TestShouldNotHappenWithin(t *testing.T) {
fail(t, so(0, ShouldNotHappenWithin), "This assertion requires exactly 2 comparison values (you provided 0).")
fail(t, so(0, ShouldNotHappenWithin, 1, 2, 3), "This assertion requires exactly 2 comparison values (you provided 3).")
fail(t, so(0, ShouldNotHappenWithin, 1, 2), shouldUseDurationAndTime)
fail(t, so(0, ShouldNotHappenWithin, oneDay, time.Now()), shouldUseDurationAndTime)
fail(t, so(time.Now(), ShouldNotHappenWithin, 0, time.Now()), shouldUseDurationAndTime)
pass(t, so(january1, ShouldNotHappenWithin, oneDay, january3))
fail(t, so(january2, ShouldNotHappenWithin, oneDay, january3), fmt.Sprintf("Expected '%s' to NOT happen on or between '%s' and '%s' (but it did)!", pretty(january2), pretty(january2), pretty(january4)))
fail(t, so(january3, ShouldNotHappenWithin, oneDay, january3), fmt.Sprintf("Expected '%s' to NOT happen on or between '%s' and '%s' (but it did)!", pretty(january3), pretty(january2), pretty(january4)))
fail(t, so(january4, ShouldNotHappenWithin, oneDay, january3), fmt.Sprintf("Expected '%s' to NOT happen on or between '%s' and '%s' (but it did)!", pretty(january4), pretty(january2), pretty(january4)))
pass(t, so(january5, ShouldNotHappenWithin, oneDay, january3))
}
func TestShouldBeChronological(t *testing.T) {
fail(t, so(0, ShouldBeChronological, 1, 2, 3), "This assertion requires exactly 0 comparison values (you provided 3).")
fail(t, so(0, ShouldBeChronological), shouldUseTimeSlice)
fail(t, so([]time.Time{january5, january1}, ShouldBeChronological),
"The 'Time' at index [1] should have happened after the previous one (but it didn't!):\n [0]: 2013-01-05 00:00:00 +0000 UTC\n [1]: 2013-01-01 00:00:00 +0000 UTC (see, it happened before!)")
pass(t, so([]time.Time{january1, january2, january3, january4, january5}, ShouldBeChronological))
}
const layout = "2006-01-02 15:04"
var january1, _ = time.Parse(layout, "2013-01-01 00:00")
var january2, _ = time.Parse(layout, "2013-01-02 00:00")
var january3, _ = time.Parse(layout, "2013-01-03 00:00")
var january4, _ = time.Parse(layout, "2013-01-04 00:00")
var january5, _ = time.Parse(layout, "2013-01-05 00:00")
var oneDay, _ = time.ParseDuration("24h0m0s")
var twoDays, _ = time.ParseDuration("48h0m0s")
func pretty(t time.Time) string {
return fmt.Sprintf("%v", t)
}

View File

@ -1,112 +0,0 @@
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
}

View File

@ -1,76 +0,0 @@
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))
}

View File

@ -1,75 +0,0 @@
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)
}

View File

@ -1,179 +0,0 @@
// 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)

View File

@ -1,57 +0,0 @@
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())."

View File

@ -1,178 +0,0 @@
// 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...)
}

View File

@ -1,72 +0,0 @@
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)
}

View File

@ -1,37 +0,0 @@
// 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)

View File

@ -1,72 +0,0 @@
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
}

View File

@ -1,742 +0,0 @@
package convey
import (
"strconv"
"testing"
"time"
)
func TestSingleScope(t *testing.T) {
output := prepare()
Convey("hi", t, func() {
output += "done"
})
expectEqual(t, "done", output)
}
func TestSingleScopeWithMultipleConveys(t *testing.T) {
output := prepare()
Convey("1", t, func() {
output += "1"
})
Convey("2", t, func() {
output += "2"
})
expectEqual(t, "12", output)
}
func TestNestedScopes(t *testing.T) {
output := prepare()
Convey("a", t, func() {
output += "a "
Convey("bb", func() {
output += "bb "
Convey("ccc", func() {
output += "ccc | "
})
})
})
expectEqual(t, "a bb ccc | ", output)
}
func TestNestedScopesWithIsolatedExecution(t *testing.T) {
output := prepare()
Convey("a", t, func() {
output += "a "
Convey("aa", func() {
output += "aa "
Convey("aaa", func() {
output += "aaa | "
})
Convey("aaa1", func() {
output += "aaa1 | "
})
})
Convey("ab", func() {
output += "ab "
Convey("abb", func() {
output += "abb | "
})
})
})
expectEqual(t, "a aa aaa | a aa aaa1 | a ab abb | ", output)
}
func TestSingleScopeWithConveyAndNestedReset(t *testing.T) {
output := prepare()
Convey("1", t, func() {
output += "1"
Reset(func() {
output += "a"
})
})
expectEqual(t, "1a", output)
}
func TestSingleScopeWithMultipleRegistrationsAndReset(t *testing.T) {
output := prepare()
Convey("reset after each nested convey", t, func() {
Convey("first output", func() {
output += "1"
})
Convey("second output", func() {
output += "2"
})
Reset(func() {
output += "a"
})
})
expectEqual(t, "1a2a", output)
}
func TestSingleScopeWithMultipleRegistrationsAndMultipleResets(t *testing.T) {
output := prepare()
Convey("each reset is run at end of each nested convey", t, func() {
Convey("1", func() {
output += "1"
})
Convey("2", func() {
output += "2"
})
Reset(func() {
output += "a"
})
Reset(func() {
output += "b"
})
})
expectEqual(t, "1ab2ab", output)
}
func Test_Failure_AtHigherLevelScopePreventsChildScopesFromRunning(t *testing.T) {
output := prepare()
Convey("This step fails", t, func() {
So(1, ShouldEqual, 2)
Convey("this should NOT be executed", func() {
output += "a"
})
})
expectEqual(t, "", output)
}
func Test_Panic_AtHigherLevelScopePreventsChildScopesFromRunning(t *testing.T) {
output := prepare()
Convey("This step panics", t, func() {
Convey("this should NOT be executed", func() {
output += "1"
})
panic("Hi")
})
expectEqual(t, "", output)
}
func Test_Panic_InChildScopeDoes_NOT_PreventExecutionOfSiblingScopes(t *testing.T) {
output := prepare()
Convey("This is the parent", t, func() {
Convey("This step panics", func() {
panic("Hi")
output += "1"
})
Convey("This sibling should execute", func() {
output += "2"
})
})
expectEqual(t, "2", output)
}
func Test_Failure_InChildScopeDoes_NOT_PreventExecutionOfSiblingScopes(t *testing.T) {
output := prepare()
Convey("This is the parent", t, func() {
Convey("This step fails", func() {
So(1, ShouldEqual, 2)
output += "1"
})
Convey("This sibling should execute", func() {
output += "2"
})
})
expectEqual(t, "2", output)
}
func TestResetsAreAlwaysExecutedAfterScope_Panics(t *testing.T) {
output := prepare()
Convey("This is the parent", t, func() {
Convey("This step panics", func() {
panic("Hi")
output += "1"
})
Convey("This sibling step does not panic", func() {
output += "a"
Reset(func() {
output += "b"
})
})
Reset(func() {
output += "2"
})
})
expectEqual(t, "2ab2", output)
}
func TestResetsAreAlwaysExecutedAfterScope_Failures(t *testing.T) {
output := prepare()
Convey("This is the parent", t, func() {
Convey("This step fails", func() {
So(1, ShouldEqual, 2)
output += "1"
})
Convey("This sibling step does not fail", func() {
output += "a"
Reset(func() {
output += "b"
})
})
Reset(func() {
output += "2"
})
})
expectEqual(t, "2ab2", output)
}
func TestSkipTopLevel(t *testing.T) {
output := prepare()
SkipConvey("hi", t, func() {
output += "This shouldn't be executed!"
})
expectEqual(t, "", output)
}
func TestSkipNestedLevel(t *testing.T) {
output := prepare()
Convey("hi", t, func() {
output += "yes"
SkipConvey("bye", func() {
output += "no"
})
})
expectEqual(t, "yes", output)
}
func TestSkipNestedLevelSkipsAllChildLevels(t *testing.T) {
output := prepare()
Convey("hi", t, func() {
output += "yes"
SkipConvey("bye", func() {
output += "no"
Convey("byebye", func() {
output += "no-no"
})
})
})
expectEqual(t, "yes", output)
}
func TestIterativeConveys(t *testing.T) {
output := prepare()
Convey("Test", t, func() {
for x := 0; x < 10; x++ {
y := strconv.Itoa(x)
Convey(y, func() {
output += y
})
}
})
expectEqual(t, "0123456789", output)
}
func TestClosureVariables(t *testing.T) {
output := prepare()
i := 0
Convey("A", t, func() {
i = i + 1
j := i
output += "A" + strconv.Itoa(i) + " "
Convey("B", func() {
k := j
j = j + 1
output += "B" + strconv.Itoa(k) + " "
Convey("C", func() {
output += "C" + strconv.Itoa(k) + strconv.Itoa(j) + " "
})
Convey("D", func() {
output += "D" + strconv.Itoa(k) + strconv.Itoa(j) + " "
})
})
Convey("C", func() {
output += "C" + strconv.Itoa(j) + " "
})
})
output += "D" + strconv.Itoa(i) + " "
expectEqual(t, "A1 B1 C12 A2 B2 D23 A3 C3 D3 ", output)
}
func TestClosureVariablesWithReset(t *testing.T) {
output := prepare()
i := 0
Convey("A", t, func() {
i = i + 1
j := i
output += "A" + strconv.Itoa(i) + " "
Reset(func() {
output += "R" + strconv.Itoa(i) + strconv.Itoa(j) + " "
})
Convey("B", func() {
output += "B" + strconv.Itoa(j) + " "
})
Convey("C", func() {
output += "C" + strconv.Itoa(j) + " "
})
})
output += "D" + strconv.Itoa(i) + " "
expectEqual(t, "A1 B1 R11 A2 C2 R22 D2 ", output)
}
func TestWrappedSimple(t *testing.T) {
prepare()
output := resetTestString{""}
Convey("A", t, func() {
func() {
output.output += "A "
Convey("B", func() {
output.output += "B "
Convey("C", func() {
output.output += "C "
})
})
Convey("D", func() {
output.output += "D "
})
}()
})
expectEqual(t, "A B C A D ", output.output)
}
type resetTestString struct {
output string
}
func addReset(o *resetTestString, f func()) func() {
return func() {
Reset(func() {
o.output += "R "
})
f()
}
}
func TestWrappedReset(t *testing.T) {
prepare()
output := resetTestString{""}
Convey("A", t, addReset(&output, func() {
output.output += "A "
Convey("B", func() {
output.output += "B "
})
Convey("C", func() {
output.output += "C "
})
}))
expectEqual(t, "A B R A C R ", output.output)
}
func TestWrappedReset2(t *testing.T) {
prepare()
output := resetTestString{""}
Convey("A", t, func() {
Reset(func() {
output.output += "R "
})
func() {
output.output += "A "
Convey("B", func() {
output.output += "B "
Convey("C", func() {
output.output += "C "
})
})
Convey("D", func() {
output.output += "D "
})
}()
})
expectEqual(t, "A B C R A D R ", output.output)
}
func TestInfiniteLoopWithTrailingFail(t *testing.T) {
done := make(chan int)
go func() {
Convey("This fails", t, func() {
Convey("and this is run", func() {
So(true, ShouldEqual, true)
})
/* And this prevents the whole block to be marked as run */
So(false, ShouldEqual, true)
})
done <- 1
}()
select {
case <-done:
return
case <-time.After(1 * time.Millisecond):
t.Fail()
}
}
func TestOutermostResetInvokedForGrandchildren(t *testing.T) {
output := prepare()
Convey("A", t, func() {
output += "A "
Reset(func() {
output += "rA "
})
Convey("B", func() {
output += "B "
Reset(func() {
output += "rB "
})
Convey("C", func() {
output += "C "
Reset(func() {
output += "rC "
})
})
Convey("D", func() {
output += "D "
Reset(func() {
output += "rD "
})
})
})
})
expectEqual(t, "A B C rC rB rA A B D rD rB rA ", output)
}
func TestFailureOption(t *testing.T) {
output := prepare()
Convey("A", t, FailureHalts, func() {
output += "A "
So(true, ShouldEqual, true)
output += "B "
So(false, ShouldEqual, true)
output += "C "
})
expectEqual(t, "A B ", output)
}
func TestFailureOption2(t *testing.T) {
output := prepare()
Convey("A", t, func() {
output += "A "
So(true, ShouldEqual, true)
output += "B "
So(false, ShouldEqual, true)
output += "C "
})
expectEqual(t, "A B ", output)
}
func TestFailureOption3(t *testing.T) {
output := prepare()
Convey("A", t, FailureContinues, func() {
output += "A "
So(true, ShouldEqual, true)
output += "B "
So(false, ShouldEqual, true)
output += "C "
})
expectEqual(t, "A B C ", output)
}
func TestFailureOptionInherit(t *testing.T) {
output := prepare()
Convey("A", t, FailureContinues, func() {
output += "A1 "
So(false, ShouldEqual, true)
output += "A2 "
Convey("B", func() {
output += "B1 "
So(true, ShouldEqual, true)
output += "B2 "
So(false, ShouldEqual, true)
output += "B3 "
})
})
expectEqual(t, "A1 A2 B1 B2 B3 ", output)
}
func TestFailureOptionInherit2(t *testing.T) {
output := prepare()
Convey("A", t, FailureHalts, func() {
output += "A1 "
So(false, ShouldEqual, true)
output += "A2 "
Convey("B", func() {
output += "A1 "
So(true, ShouldEqual, true)
output += "A2 "
So(false, ShouldEqual, true)
output += "A3 "
})
})
expectEqual(t, "A1 ", output)
}
func TestFailureOptionInherit3(t *testing.T) {
output := prepare()
Convey("A", t, FailureHalts, func() {
output += "A1 "
So(true, ShouldEqual, true)
output += "A2 "
Convey("B", func() {
output += "B1 "
So(true, ShouldEqual, true)
output += "B2 "
So(false, ShouldEqual, true)
output += "B3 "
})
})
expectEqual(t, "A1 A2 B1 B2 ", output)
}
func TestFailureOptionNestedOverride(t *testing.T) {
output := prepare()
Convey("A", t, FailureContinues, func() {
output += "A "
So(false, ShouldEqual, true)
output += "B "
Convey("C", FailureHalts, func() {
output += "C "
So(true, ShouldEqual, true)
output += "D "
So(false, ShouldEqual, true)
output += "E "
})
})
expectEqual(t, "A B C D ", output)
}
func TestFailureOptionNestedOverride2(t *testing.T) {
output := prepare()
Convey("A", t, FailureHalts, func() {
output += "A "
So(true, ShouldEqual, true)
output += "B "
Convey("C", FailureContinues, func() {
output += "C "
So(true, ShouldEqual, true)
output += "D "
So(false, ShouldEqual, true)
output += "E "
})
})
expectEqual(t, "A B C D E ", output)
}
func TestMultipleInvocationInheritance(t *testing.T) {
output := prepare()
Convey("A", t, FailureHalts, func() {
output += "A1 "
So(true, ShouldEqual, true)
output += "A2 "
Convey("B", FailureContinues, func() {
output += "B1 "
So(true, ShouldEqual, true)
output += "B2 "
So(false, ShouldEqual, true)
output += "B3 "
})
Convey("C", func() {
output += "C1 "
So(true, ShouldEqual, true)
output += "C2 "
So(false, ShouldEqual, true)
output += "C3 "
})
})
expectEqual(t, "A1 A2 B1 B2 B3 A1 A2 C1 C2 ", output)
}
func TestMultipleInvocationInheritance2(t *testing.T) {
output := prepare()
Convey("A", t, FailureContinues, func() {
output += "A1 "
So(true, ShouldEqual, true)
output += "A2 "
So(false, ShouldEqual, true)
output += "A3 "
Convey("B", FailureHalts, func() {
output += "B1 "
So(true, ShouldEqual, true)
output += "B2 "
So(false, ShouldEqual, true)
output += "B3 "
})
Convey("C", func() {
output += "C1 "
So(true, ShouldEqual, true)
output += "C2 "
So(false, ShouldEqual, true)
output += "C3 "
})
})
expectEqual(t, "A1 A2 A3 B1 B2 A1 A2 A3 C1 C2 C3 ", output)
}
func TestSetDefaultFailureMode(t *testing.T) {
output := prepare()
SetDefaultFailureMode(FailureContinues) // the default is normally FailureHalts
defer SetDefaultFailureMode(FailureHalts)
Convey("A", t, func() {
output += "A1 "
So(true, ShouldBeFalse)
output += "A2 "
})
expectEqual(t, "A1 A2 ", output)
}
func prepare() string {
testReporter = newNilReporter()
return ""
}

View File

@ -1,74 +0,0 @@
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()
}

View File

@ -1,16 +0,0 @@
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)
}

View File

@ -1,5 +0,0 @@
// 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

View File

@ -1,40 +0,0 @@
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
}

View File

@ -1,40 +0,0 @@
package reporting
import (
"errors"
"testing"
)
func TestDotReporterAssertionPrinting(t *testing.T) {
monochrome()
file := newMemoryFile()
printer := NewPrinter(file)
reporter := NewDotReporter(printer)
reporter.Report(NewSuccessReport())
reporter.Report(NewFailureReport("failed"))
reporter.Report(NewErrorReport(errors.New("error")))
reporter.Report(NewSkipReport())
expected := dotSuccess + dotFailure + dotError + dotSkip
if file.buffer != expected {
t.Errorf("\nExpected: '%s'\nActual: '%s'", expected, file.buffer)
}
}
func TestDotReporterOnlyReportsAssertions(t *testing.T) {
monochrome()
file := newMemoryFile()
printer := NewPrinter(file)
reporter := NewDotReporter(printer)
reporter.BeginStory(nil)
reporter.Enter(nil)
reporter.Exit()
reporter.EndStory()
if file.buffer != "" {
t.Errorf("\nExpected: '(blank)'\nActual: '%s'", file.buffer)
}
}

View File

@ -1,33 +0,0 @@
package reporting
type gotestReporter struct{ test T }
func (self *gotestReporter) BeginStory(story *StoryReport) {
self.test = story.Test
}
func (self *gotestReporter) Enter(scope *ScopeReport) {}
func (self *gotestReporter) Report(r *AssertionResult) {
if !passed(r) {
self.test.Fail()
}
}
func (self *gotestReporter) Exit() {}
func (self *gotestReporter) EndStory() {
self.test = nil
}
func (self *gotestReporter) Write(content []byte) (written int, err error) {
return len(content), nil // no-op
}
func NewGoTestReporter() *gotestReporter {
return new(gotestReporter)
}
func passed(r *AssertionResult) bool {
return r.Error == nil && r.Failure == ""
}

View File

@ -1,66 +0,0 @@
package reporting
import "testing"
func TestReporterReceivesSuccessfulReport(t *testing.T) {
reporter := NewGoTestReporter()
test := new(fakeTest)
reporter.BeginStory(NewStoryReport(test))
reporter.Report(NewSuccessReport())
if test.failed {
t.Errorf("Should have have marked test as failed--the report reflected success.")
}
}
func TestReporterReceivesFailureReport(t *testing.T) {
reporter := NewGoTestReporter()
test := new(fakeTest)
reporter.BeginStory(NewStoryReport(test))
reporter.Report(NewFailureReport("This is a failure."))
if !test.failed {
t.Errorf("Test should have been marked as failed (but it wasn't).")
}
}
func TestReporterReceivesErrorReport(t *testing.T) {
reporter := NewGoTestReporter()
test := new(fakeTest)
reporter.BeginStory(NewStoryReport(test))
reporter.Report(NewErrorReport("This is an error."))
if !test.failed {
t.Errorf("Test should have been marked as failed (but it wasn't).")
}
}
func TestReporterIsResetAtTheEndOfTheStory(t *testing.T) {
defer catch(t)
reporter := NewGoTestReporter()
test := new(fakeTest)
reporter.BeginStory(NewStoryReport(test))
reporter.EndStory()
reporter.Report(NewSuccessReport())
}
func TestReporterNoopMethods(t *testing.T) {
reporter := NewGoTestReporter()
reporter.Enter(NewScopeReport("title", "name"))
reporter.Exit()
}
func catch(t *testing.T) {
if r := recover(); r != nil {
t.Log("Getting to this point means we've passed (because we caught a panic appropriately).")
}
}
type fakeTest struct {
failed bool
}
func (self *fakeTest) Fail() {
self.failed = true
}

View File

@ -1,94 +0,0 @@
package reporting
import (
"fmt"
"os"
"runtime"
"strings"
)
func init() {
if !isXterm() {
monochrome()
}
if runtime.GOOS == "windows" {
success, failure, error_ = dotSuccess, dotFailure, dotError
}
}
func BuildJsonReporter() Reporter {
out := NewPrinter(NewConsole())
return NewReporters(
NewGoTestReporter(),
NewJsonReporter(out))
}
func BuildDotReporter() Reporter {
out := NewPrinter(NewConsole())
return NewReporters(
NewGoTestReporter(),
NewDotReporter(out),
NewProblemReporter(out),
consoleStatistics)
}
func BuildStoryReporter() Reporter {
out := NewPrinter(NewConsole())
return NewReporters(
NewGoTestReporter(),
NewStoryReporter(out),
NewProblemReporter(out),
consoleStatistics)
}
func BuildSilentReporter() Reporter {
out := NewPrinter(NewConsole())
return NewReporters(
NewGoTestReporter(),
NewProblemReporter(out))
}
var (
newline = "\n"
success = "✔"
failure = "✘"
error_ = "🔥"
skip = "⚠"
dotSuccess = "."
dotFailure = "x"
dotError = "E"
dotSkip = "S"
errorTemplate = "* %s \nLine %d: - %v \n%s\n"
failureTemplate = "* %s \nLine %d:\n%s\n"
)
var (
greenColor = "\033[32m"
yellowColor = "\033[33m"
redColor = "\033[31m"
resetColor = "\033[0m"
)
var consoleStatistics = NewStatisticsReporter(NewPrinter(NewConsole()))
// QuiteMode disables all console output symbols. This is only meant to be used
// for tests that are internal to goconvey where the output is distracting or
// otherwise not needed in the test output.
func QuietMode() {
success, failure, error_, skip, dotSuccess, dotFailure, dotError, dotSkip = "", "", "", "", "", "", "", ""
}
func monochrome() {
greenColor, yellowColor, redColor, resetColor = "", "", "", ""
}
func isXterm() bool {
env := fmt.Sprintf("%v", os.Environ())
return strings.Contains(env, " TERM=isXterm") ||
strings.Contains(env, " TERM=xterm")
}
// 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()
}

View File

@ -1,88 +0,0 @@
// TODO: under unit test
package reporting
import (
"bytes"
"encoding/json"
"fmt"
"strings"
)
type JsonReporter struct {
out *Printer
current *ScopeResult
index map[string]*ScopeResult
scopes []*ScopeResult
depth int
}
func (self *JsonReporter) BeginStory(story *StoryReport) {}
func (self *JsonReporter) Enter(scope *ScopeReport) {
if _, found := self.index[scope.ID]; !found {
self.registerScope(scope)
}
self.depth++
self.current = self.index[scope.ID]
}
func (self *JsonReporter) registerScope(scope *ScopeReport) {
next := newScopeResult(scope.Title, self.depth, scope.File, scope.Line)
self.scopes = append(self.scopes, next)
self.index[scope.ID] = next
}
func (self *JsonReporter) Report(report *AssertionResult) {
self.current.Assertions = append(self.current.Assertions, report)
}
func (self *JsonReporter) Exit() {
self.depth--
}
func (self *JsonReporter) EndStory() {
self.report()
self.reset()
}
func (self *JsonReporter) report() {
scopes := []string{}
for _, scope := range self.scopes {
serialized, err := json.Marshal(scope)
if err != nil {
self.out.Println(jsonMarshalFailure)
panic(err)
}
var buffer bytes.Buffer
json.Indent(&buffer, serialized, "", " ")
scopes = append(scopes, buffer.String())
}
self.out.Print(fmt.Sprintf("%s\n%s,\n%s\n", OpenJson, strings.Join(scopes, ","), CloseJson))
}
func (self *JsonReporter) reset() {
self.scopes = []*ScopeResult{}
self.index = map[string]*ScopeResult{}
self.depth = 0
}
func (self *JsonReporter) Write(content []byte) (written int, err error) {
self.current.Output += string(content)
return len(content), nil
}
func NewJsonReporter(out *Printer) *JsonReporter {
self := new(JsonReporter)
self.out = out
self.reset()
return self
}
const OpenJson = ">>>>>" // "⌦"
const CloseJson = "<<<<<" // "⌫"
const jsonMarshalFailure = `
GOCONVEY_JSON_MARSHALL_FAILURE: There was an error when attempting to convert test results to JSON.
Please file a bug report and reference the code that caused this failure if possible.
Here's the panic:
`

View File

@ -1,57 +0,0 @@
package reporting
import (
"fmt"
"io"
"strings"
)
type Printer struct {
out io.Writer
prefix string
}
func (self *Printer) Println(message string, values ...interface{}) {
formatted := self.format(message, values...) + newline
self.out.Write([]byte(formatted))
}
func (self *Printer) Print(message string, values ...interface{}) {
formatted := self.format(message, values...)
self.out.Write([]byte(formatted))
}
func (self *Printer) Insert(text string) {
self.out.Write([]byte(text))
}
func (self *Printer) format(message string, values ...interface{}) string {
var formatted string
if len(values) == 0 {
formatted = self.prefix + message
} else {
formatted = self.prefix + fmt.Sprintf(message, values...)
}
indented := strings.Replace(formatted, newline, newline+self.prefix, -1)
return strings.TrimRight(indented, space)
}
func (self *Printer) Indent() {
self.prefix += pad
}
func (self *Printer) Dedent() {
if len(self.prefix) >= padLength {
self.prefix = self.prefix[:len(self.prefix)-padLength]
}
}
func NewPrinter(out io.Writer) *Printer {
self := new(Printer)
self.out = out
return self
}
const space = " "
const pad = space + space
const padLength = len(pad)

View File

@ -1,181 +0,0 @@
package reporting
import "testing"
func TestPrint(t *testing.T) {
file := newMemoryFile()
printer := NewPrinter(file)
const expected = "Hello, World!"
printer.Print(expected)
if file.buffer != expected {
t.Errorf("Expected '%s' to equal '%s'.", expected, file.buffer)
}
}
func TestPrintFormat(t *testing.T) {
file := newMemoryFile()
printer := NewPrinter(file)
template := "Hi, %s"
name := "Ralph"
expected := "Hi, Ralph"
printer.Print(template, name)
if file.buffer != expected {
t.Errorf("Expected '%s' to equal '%s'.", expected, file.buffer)
}
}
func TestPrintPreservesEncodedStrings(t *testing.T) {
file := newMemoryFile()
printer := NewPrinter(file)
const expected = "= -> %3D"
printer.Print(expected)
if file.buffer != expected {
t.Errorf("Expected '%s' to equal '%s'.", expected, file.buffer)
}
}
func TestPrintln(t *testing.T) {
file := newMemoryFile()
printer := NewPrinter(file)
const expected = "Hello, World!"
printer.Println(expected)
if file.buffer != expected+"\n" {
t.Errorf("Expected '%s' to equal '%s'.", expected, file.buffer)
}
}
func TestPrintlnFormat(t *testing.T) {
file := newMemoryFile()
printer := NewPrinter(file)
template := "Hi, %s"
name := "Ralph"
expected := "Hi, Ralph\n"
printer.Println(template, name)
if file.buffer != expected {
t.Errorf("Expected '%s' to equal '%s'.", expected, file.buffer)
}
}
func TestPrintlnPreservesEncodedStrings(t *testing.T) {
file := newMemoryFile()
printer := NewPrinter(file)
const expected = "= -> %3D"
printer.Println(expected)
if file.buffer != expected+"\n" {
t.Errorf("Expected '%s' to equal '%s'.", expected, file.buffer)
}
}
func TestPrintIndented(t *testing.T) {
file := newMemoryFile()
printer := NewPrinter(file)
const message = "Hello, World!\nGoodbye, World!"
const expected = " Hello, World!\n Goodbye, World!"
printer.Indent()
printer.Print(message)
if file.buffer != expected {
t.Errorf("Expected '%s' to equal '%s'.", expected, file.buffer)
}
}
func TestPrintDedented(t *testing.T) {
file := newMemoryFile()
printer := NewPrinter(file)
const expected = "Hello, World!\nGoodbye, World!"
printer.Indent()
printer.Dedent()
printer.Print(expected)
if file.buffer != expected {
t.Errorf("Expected '%s' to equal '%s'.", expected, file.buffer)
}
}
func TestPrintlnIndented(t *testing.T) {
file := newMemoryFile()
printer := NewPrinter(file)
const message = "Hello, World!\nGoodbye, World!"
const expected = " Hello, World!\n Goodbye, World!\n"
printer.Indent()
printer.Println(message)
if file.buffer != expected {
t.Errorf("Expected '%s' to equal '%s'.", expected, file.buffer)
}
}
func TestPrintlnDedented(t *testing.T) {
file := newMemoryFile()
printer := NewPrinter(file)
const expected = "Hello, World!\nGoodbye, World!"
printer.Indent()
printer.Dedent()
printer.Println(expected)
if file.buffer != expected+"\n" {
t.Errorf("Expected '%s' to equal '%s'.", expected, file.buffer)
}
}
func TestDedentTooFarShouldNotPanic(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Error("Should not have panicked!")
}
}()
file := newMemoryFile()
printer := NewPrinter(file)
printer.Dedent()
t.Log("Getting to this point without panicking means we passed.")
}
func TestInsert(t *testing.T) {
file := newMemoryFile()
printer := NewPrinter(file)
printer.Indent()
printer.Print("Hi")
printer.Insert(" there")
printer.Dedent()
expected := " Hi there"
if file.buffer != expected {
t.Errorf("Should have written '%s' but instead wrote '%s'.", expected, file.buffer)
}
}
////////////////// memoryFile ////////////////////
type memoryFile struct {
buffer string
}
func (self *memoryFile) Write(p []byte) (n int, err error) {
self.buffer += string(p)
return len(p), nil
}
func (self *memoryFile) String() string {
return self.buffer
}
func newMemoryFile() *memoryFile {
return new(memoryFile)
}

View File

@ -1,68 +0,0 @@
package reporting
import "fmt"
type problem struct {
out *Printer
errors []*AssertionResult
failures []*AssertionResult
}
func (self *problem) BeginStory(story *StoryReport) {}
func (self *problem) Enter(scope *ScopeReport) {}
func (self *problem) Report(report *AssertionResult) {
if report.Error != nil {
self.errors = append(self.errors, report)
} else if report.Failure != "" {
self.failures = append(self.failures, report)
}
}
func (self *problem) Exit() {}
func (self *problem) EndStory() {
self.show(self.showErrors, redColor)
self.show(self.showFailures, yellowColor)
self.prepareForNextStory()
}
func (self *problem) show(display func(), color string) {
fmt.Print(color)
display()
fmt.Print(resetColor)
self.out.Dedent()
}
func (self *problem) showErrors() {
for i, e := range self.errors {
if i == 0 {
self.out.Println("\nErrors:\n")
self.out.Indent()
}
self.out.Println(errorTemplate, e.File, e.Line, e.Error, e.StackTrace)
}
}
func (self *problem) showFailures() {
for i, f := range self.failures {
if i == 0 {
self.out.Println("\nFailures:\n")
self.out.Indent()
}
self.out.Println(failureTemplate, f.File, f.Line, f.Failure)
}
}
func (self *problem) Write(content []byte) (written int, err error) {
return len(content), nil // no-op
}
func NewProblemReporter(out *Printer) *problem {
self := new(problem)
self.out = out
self.prepareForNextStory()
return self
}
func (self *problem) prepareForNextStory() {
self.errors = []*AssertionResult{}
self.failures = []*AssertionResult{}
}

View File

@ -1,46 +0,0 @@
package reporting
import (
"strings"
"testing"
)
func TestNoopProblemReporterActions(t *testing.T) {
file, reporter := setup()
reporter.BeginStory(nil)
reporter.Enter(nil)
reporter.Exit()
expected := ""
actual := file.String()
if expected != actual {
t.Errorf("Expected: '(blank)'\nActual: '%s'", actual)
}
}
func TestReporterPrintsFailuresAndErrorsAtTheEndOfTheStory(t *testing.T) {
file, reporter := setup()
reporter.Report(NewFailureReport("failed"))
reporter.Report(NewErrorReport("error"))
reporter.Report(NewSuccessReport())
reporter.EndStory()
result := file.String()
if !strings.Contains(result, "Errors:\n") {
t.Errorf("Expected errors, found none.")
}
if !strings.Contains(result, "Failures:\n") {
t.Errorf("Expected failures, found none.")
}
problemCount := strings.Count(result, "*")
if problemCount != 2 {
t.Errorf("Expected one failure and one error (total of 2 '*' characters). Got %d", problemCount)
}
}
func setup() (file *memoryFile, reporter *problem) {
monochrome()
file = newMemoryFile()
printer := NewPrinter(file)
reporter = NewProblemReporter(printer)
return
}

View File

@ -1,39 +0,0 @@
package reporting
import "io"
type Reporter interface {
BeginStory(story *StoryReport)
Enter(scope *ScopeReport)
Report(r *AssertionResult)
Exit()
EndStory()
io.Writer
}
type reporters struct{ collection []Reporter }
func (self *reporters) BeginStory(s *StoryReport) { self.foreach(func(r Reporter) { r.BeginStory(s) }) }
func (self *reporters) Enter(s *ScopeReport) { self.foreach(func(r Reporter) { r.Enter(s) }) }
func (self *reporters) Report(a *AssertionResult) { self.foreach(func(r Reporter) { r.Report(a) }) }
func (self *reporters) Exit() { self.foreach(func(r Reporter) { r.Exit() }) }
func (self *reporters) EndStory() { self.foreach(func(r Reporter) { r.EndStory() }) }
func (self *reporters) Write(contents []byte) (written int, err error) {
self.foreach(func(r Reporter) {
written, err = r.Write(contents)
})
return written, err
}
func (self *reporters) foreach(action func(Reporter)) {
for _, r := range self.collection {
action(r)
}
}
func NewReporters(collection ...Reporter) *reporters {
self := new(reporters)
self.collection = collection
return self
}

View File

@ -1,94 +0,0 @@
package reporting
import (
"runtime"
"testing"
)
func TestEachNestedReporterReceivesTheCallFromTheContainingReporter(t *testing.T) {
fake1 := newFakeReporter()
fake2 := newFakeReporter()
reporter := NewReporters(fake1, fake2)
reporter.BeginStory(nil)
assertTrue(t, fake1.begun)
assertTrue(t, fake2.begun)
reporter.Enter(NewScopeReport("scope", "hi"))
assertTrue(t, fake1.entered)
assertTrue(t, fake2.entered)
reporter.Report(NewSuccessReport())
assertTrue(t, fake1.reported)
assertTrue(t, fake2.reported)
reporter.Exit()
assertTrue(t, fake1.exited)
assertTrue(t, fake2.exited)
reporter.EndStory()
assertTrue(t, fake1.ended)
assertTrue(t, fake2.ended)
content := []byte("hi")
written, err := reporter.Write(content)
assertTrue(t, fake1.written)
assertTrue(t, fake2.written)
assertEqual(t, written, len(content))
assertNil(t, err)
}
func assertTrue(t *testing.T, value bool) {
if !value {
_, _, line, _ := runtime.Caller(1)
t.Errorf("Value should have been true (but was false). See line %d", line)
}
}
func assertEqual(t *testing.T, expected, actual int) {
if actual != expected {
_, _, line, _ := runtime.Caller(1)
t.Errorf("Value should have been %d (but was %d). See line %d", expected, actual, line)
}
}
func assertNil(t *testing.T, err error) {
if err != nil {
_, _, line, _ := runtime.Caller(1)
t.Errorf("Error should have been <nil> (but wasn't). See line %d", err, line)
}
}
type fakeReporter struct {
begun bool
entered bool
reported bool
exited bool
ended bool
written bool
}
func newFakeReporter() *fakeReporter {
return &fakeReporter{}
}
func (self *fakeReporter) BeginStory(story *StoryReport) {
self.begun = true
}
func (self *fakeReporter) Enter(scope *ScopeReport) {
self.entered = true
}
func (self *fakeReporter) Report(report *AssertionResult) {
self.reported = true
}
func (self *fakeReporter) Exit() {
self.exited = true
}
func (self *fakeReporter) EndStory() {
self.ended = true
}
func (self *fakeReporter) Write(content []byte) (int, error) {
self.written = true
return len(content), nil
}

View File

@ -1,179 +0,0 @@
package reporting
import (
"encoding/json"
"fmt"
"runtime"
"strings"
"github.com/smartystreets/goconvey/convey/gotest"
)
////////////////// ScopeReport ////////////////////
type ScopeReport struct {
Title string
ID string
File string
Line int
}
func NewScopeReport(title, name string) *ScopeReport {
file, line, _ := gotest.ResolveExternalCaller()
self := new(ScopeReport)
self.Title = title
self.ID = fmt.Sprintf("%s|%s", title, name)
self.File = file
self.Line = line
return self
}
////////////////// ScopeResult ////////////////////
type ScopeResult struct {
Title string
File string
Line int
Depth int
Assertions []*AssertionResult
Output string
}
func newScopeResult(title string, depth int, file string, line int) *ScopeResult {
self := new(ScopeResult)
self.Title = title
self.Depth = depth
self.File = file
self.Line = line
self.Assertions = []*AssertionResult{}
return self
}
/////////////////// StoryReport /////////////////////
type StoryReport struct {
Test T
Name string
File string
Line int
}
func NewStoryReport(test T) *StoryReport {
file, line, name := gotest.ResolveExternalCaller()
name = removePackagePath(name)
self := new(StoryReport)
self.Test = test
self.Name = name
self.File = file
self.Line = line
return self
}
// name comes in looking like "github.com/smartystreets/goconvey/examples.TestName".
// We only want the stuff after the last '.', which is the name of the test function.
func removePackagePath(name string) string {
parts := strings.Split(name, ".")
return parts[len(parts)-1]
}
/////////////////// FailureView ////////////////////////
type FailureView struct {
Message string
Expected string
Actual string
}
////////////////////AssertionResult //////////////////////
type AssertionResult struct {
File string
Line int
Expected string
Actual string
Failure string
Error interface{}
StackTrace string
Skipped bool
}
func NewFailureReport(failure string) *AssertionResult {
report := new(AssertionResult)
report.File, report.Line = caller()
report.StackTrace = stackTrace()
parseFailure(failure, report)
return report
}
func parseFailure(failure string, report *AssertionResult) {
view := new(FailureView)
err := json.Unmarshal([]byte(failure), view)
if err == nil {
report.Failure = view.Message
report.Expected = view.Expected
report.Actual = view.Actual
} else {
report.Failure = failure
}
}
func NewErrorReport(err interface{}) *AssertionResult {
report := new(AssertionResult)
report.File, report.Line = caller()
report.StackTrace = fullStackTrace()
report.Error = fmt.Sprintf("%v", err)
return report
}
func NewSuccessReport() *AssertionResult {
return new(AssertionResult)
}
func NewSkipReport() *AssertionResult {
report := new(AssertionResult)
report.File, report.Line = caller()
report.StackTrace = fullStackTrace()
report.Skipped = true
return report
}
func caller() (file string, line int) {
file, line, _ = gotest.ResolveExternalCaller()
return
}
func stackTrace() string {
buffer := make([]byte, 1024*64)
n := runtime.Stack(buffer, false)
return removeInternalEntries(string(buffer[:n]))
}
func fullStackTrace() string {
buffer := make([]byte, 1024*64)
n := runtime.Stack(buffer, true)
return removeInternalEntries(string(buffer[:n]))
}
func removeInternalEntries(stack string) string {
lines := strings.Split(stack, newline)
filtered := []string{}
for _, line := range lines {
if !isExternal(line) {
filtered = append(filtered, line)
}
}
return strings.Join(filtered, newline)
}
func isExternal(line string) bool {
for _, p := range internalPackages {
if strings.Contains(line, p) {
return true
}
}
return false
}
// NOTE: any new packages that host goconvey packages will need to be added here!
// An alternative is to scan the goconvey directory and then exclude stuff like
// the examples package but that's nasty too.
var internalPackages = []string{
"goconvey/assertions",
"goconvey/convey",
"goconvey/execution",
"goconvey/gotest",
"goconvey/reporting",
}

View File

@ -1,79 +0,0 @@
package reporting
import "fmt"
func (self *statistics) BeginStory(story *StoryReport) {}
func (self *statistics) Enter(scope *ScopeReport) {}
func (self *statistics) Report(report *AssertionResult) {
if !self.failing && report.Failure != "" {
self.failing = true
}
if !self.erroring && report.Error != nil {
self.erroring = true
}
if report.Skipped {
self.skipped += 1
} else {
self.total++
}
}
func (self *statistics) Exit() {}
func (self *statistics) EndStory() {
self.reportAssertions()
self.reportSkippedSections()
self.completeReport()
}
func (self *statistics) reportAssertions() {
self.decideColor()
self.out.Print("\n%d %s thus far", self.total, plural("assertion", self.total))
}
func (self *statistics) decideColor() {
if self.failing && !self.erroring {
fmt.Print(yellowColor)
} else if self.erroring {
fmt.Print(redColor)
} else {
fmt.Print(greenColor)
}
}
func (self *statistics) reportSkippedSections() {
if self.skipped > 0 {
fmt.Print(yellowColor)
self.out.Print(" (one or more sections skipped)")
self.skipped = 0
}
}
func (self *statistics) completeReport() {
fmt.Print(resetColor)
self.out.Print("\n")
self.out.Print("\n")
}
func (self *statistics) Write(content []byte) (written int, err error) {
return len(content), nil // no-op
}
func NewStatisticsReporter(out *Printer) *statistics {
self := statistics{}
self.out = out
return &self
}
type statistics struct {
out *Printer
total int
failing bool
erroring bool
skipped int
}
func plural(word string, count int) string {
if count == 1 {
return word
}
return word + "s"
}

View File

@ -1,65 +0,0 @@
// TODO: in order for this reporter to be completely honest
// we need to retrofit to be more like the json reporter such that:
// 1. it maintains ScopeResult collections, which count assertions
// 2. it reports only after EndStory(), so that all tick marks
// are placed near the appropriate title.
// 3. Under unit test
package reporting
import "fmt"
type story struct {
out *Printer
titlesById map[string]string
}
func (self *story) BeginStory(story *StoryReport) {}
func (self *story) Enter(scope *ScopeReport) {
self.out.Indent()
if _, found := self.titlesById[scope.ID]; !found {
self.out.Println("")
self.out.Print(scope.Title)
self.out.Insert(" ")
self.titlesById[scope.ID] = scope.Title
}
}
func (self *story) Report(report *AssertionResult) {
if report.Error != nil {
fmt.Print(redColor)
self.out.Insert(error_)
} else if report.Failure != "" {
fmt.Print(yellowColor)
self.out.Insert(failure)
} else if report.Skipped {
fmt.Print(yellowColor)
self.out.Insert(skip)
} else {
fmt.Print(greenColor)
self.out.Insert(success)
}
fmt.Print(resetColor)
}
func (self *story) Exit() {
self.out.Dedent()
}
func (self *story) EndStory() {
self.titlesById = make(map[string]string)
self.out.Println("\n")
}
func (self *story) Write(content []byte) (written int, err error) {
return len(content), nil // no-op
}
func NewStoryReporter(out *Printer) *story {
self := new(story)
self.out = out
self.titlesById = make(map[string]string)
return self
}

View File

@ -1,270 +0,0 @@
package convey
import (
"fmt"
"net/http"
"net/http/httptest"
"path"
"runtime"
"strconv"
"strings"
"testing"
"github.com/smartystreets/goconvey/convey/reporting"
)
func TestSingleScopeReported(t *testing.T) {
myReporter, test := setupFakeReporter()
Convey("A", test, func() {
So(1, ShouldEqual, 1)
})
expectEqual(t, "Begin|A|Success|Exit|End", myReporter.wholeStory())
}
func TestNestedScopeReported(t *testing.T) {
myReporter, test := setupFakeReporter()
Convey("A", test, func() {
Convey("B", func() {
So(1, ShouldEqual, 1)
})
})
expectEqual(t, "Begin|A|B|Success|Exit|Exit|End", myReporter.wholeStory())
}
func TestFailureReported(t *testing.T) {
myReporter, test := setupFakeReporter()
Convey("A", test, func() {
So(1, ShouldBeNil)
})
expectEqual(t, "Begin|A|Failure|Exit|End", myReporter.wholeStory())
}
func TestFirstFailureEndsScopeExecution(t *testing.T) {
myReporter, test := setupFakeReporter()
Convey("A", test, func() {
So(1, ShouldBeNil)
So(nil, ShouldBeNil)
})
expectEqual(t, "Begin|A|Failure|Exit|End", myReporter.wholeStory())
}
func TestComparisonFailureDeserializedAndReported(t *testing.T) {
myReporter, test := setupFakeReporter()
Convey("A", test, func() {
So("hi", ShouldEqual, "bye")
})
expectEqual(t, "Begin|A|Failure(bye/hi)|Exit|End", myReporter.wholeStory())
}
func TestNestedFailureReported(t *testing.T) {
myReporter, test := setupFakeReporter()
Convey("A", test, func() {
Convey("B", func() {
So(2, ShouldBeNil)
})
})
expectEqual(t, "Begin|A|B|Failure|Exit|Exit|End", myReporter.wholeStory())
}
func TestSuccessAndFailureReported(t *testing.T) {
myReporter, test := setupFakeReporter()
Convey("A", test, func() {
So(nil, ShouldBeNil)
So(1, ShouldBeNil)
})
expectEqual(t, "Begin|A|Success|Failure|Exit|End", myReporter.wholeStory())
}
func TestIncompleteActionReportedAsSkipped(t *testing.T) {
myReporter, test := setupFakeReporter()
Convey("A", test, func() {
Convey("B", nil)
})
expectEqual(t, "Begin|A|B|Skipped|Exit|Exit|End", myReporter.wholeStory())
}
func TestSkippedConveyReportedAsSkipped(t *testing.T) {
myReporter, test := setupFakeReporter()
Convey("A", test, func() {
SkipConvey("B", func() {
So(1, ShouldEqual, 1)
})
})
expectEqual(t, "Begin|A|B|Skipped|Exit|Exit|End", myReporter.wholeStory())
}
func TestMultipleSkipsAreReported(t *testing.T) {
myReporter, test := setupFakeReporter()
Convey("A", test, func() {
Convey("0", func() {
So(nil, ShouldBeNil)
})
SkipConvey("1", func() {})
SkipConvey("2", func() {})
Convey("3", nil)
Convey("4", nil)
Convey("5", func() {
So(nil, ShouldBeNil)
})
})
expected := "Begin" +
"|A|0|Success|Exit|Exit" +
"|A|1|Skipped|Exit|Exit" +
"|A|2|Skipped|Exit|Exit" +
"|A|3|Skipped|Exit|Exit" +
"|A|4|Skipped|Exit|Exit" +
"|A|5|Success|Exit|Exit" +
"|End"
expectEqual(t, expected, myReporter.wholeStory())
}
func TestSkippedAssertionIsNotReported(t *testing.T) {
myReporter, test := setupFakeReporter()
Convey("A", test, func() {
SkipSo(1, ShouldEqual, 1)
})
expectEqual(t, "Begin|A|Skipped|Exit|End", myReporter.wholeStory())
}
func TestMultipleSkippedAssertionsAreNotReported(t *testing.T) {
myReporter, test := setupFakeReporter()
Convey("A", test, func() {
SkipSo(1, ShouldEqual, 1)
So(1, ShouldEqual, 1)
SkipSo(1, ShouldEqual, 1)
})
expectEqual(t, "Begin|A|Skipped|Success|Skipped|Exit|End", myReporter.wholeStory())
}
func TestErrorByManualPanicReported(t *testing.T) {
myReporter, test := setupFakeReporter()
Convey("A", test, func() {
panic("Gopher alert!")
})
expectEqual(t, "Begin|A|Error|Exit|End", myReporter.wholeStory())
}
func TestIterativeConveysReported(t *testing.T) {
myReporter, test := setupFakeReporter()
Convey("A", test, func() {
for x := 0; x < 3; x++ {
Convey(strconv.Itoa(x), func() {
So(x, ShouldEqual, x)
})
}
})
expectEqual(t, "Begin|A|0|Success|Exit|Exit|A|1|Success|Exit|Exit|A|2|Success|Exit|Exit|End", myReporter.wholeStory())
}
func TestEmbeddedAssertionReported(t *testing.T) {
myReporter, test := setupFakeReporter()
Convey("A", test, func() {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
So(r.FormValue("msg"), ShouldEqual, "ping")
}))
http.DefaultClient.Get(ts.URL + "?msg=ping")
})
expectEqual(t, "Begin|A|Success|Exit|End", myReporter.wholeStory())
}
func expectEqual(t *testing.T, expected interface{}, actual interface{}) {
if expected != actual {
_, file, line, _ := runtime.Caller(1)
t.Errorf("Expected '%v' to be '%v' but it wasn't. See '%s' at line %d.",
actual, expected, path.Base(file), line)
}
}
func setupFakeReporter() (*fakeReporter, *fakeGoTest) {
myReporter := new(fakeReporter)
myReporter.calls = []string{}
testReporter = myReporter
return myReporter, new(fakeGoTest)
}
type fakeReporter struct {
calls []string
}
func (self *fakeReporter) BeginStory(story *reporting.StoryReport) {
self.calls = append(self.calls, "Begin")
}
func (self *fakeReporter) Enter(scope *reporting.ScopeReport) {
self.calls = append(self.calls, scope.Title)
}
func (self *fakeReporter) Report(report *reporting.AssertionResult) {
if report.Error != nil {
self.calls = append(self.calls, "Error")
} else if report.Failure != "" {
message := "Failure"
if report.Expected != "" || report.Actual != "" {
message += fmt.Sprintf("(%s/%s)", report.Expected, report.Actual)
}
self.calls = append(self.calls, message)
} else if report.Skipped {
self.calls = append(self.calls, "Skipped")
} else {
self.calls = append(self.calls, "Success")
}
}
func (self *fakeReporter) Exit() {
self.calls = append(self.calls, "Exit")
}
func (self *fakeReporter) EndStory() {
self.calls = append(self.calls, "End")
}
func (self *fakeReporter) Write(content []byte) (int, error) {
return len(content), nil // no-op
}
func (self *fakeReporter) wholeStory() string {
return strings.Join(self.calls, "|")
}
////////////////////////////////
type fakeGoTest struct{}
func (self *fakeGoTest) Fail() {}
func (self *fakeGoTest) Fatalf(format string, args ...interface{}) {}
var test t = new(fakeGoTest)

View File

@ -1,92 +0,0 @@
package convey
import (
"github.com/smartystreets/goconvey/convey/reporting"
)
type runner struct {
top *scope
active *scope
reporter reporting.Reporter
failureMode FailureMode
focus bool
}
func (self *runner) Register(entry *registration) {
if self.focus && !entry.Focus {
return
}
self.active.adopt(newScope(entry, self.reporter))
}
func (self *runner) RegisterReset(action *action) {
self.active.registerReset(action)
}
func (self *runner) Run(entry *registration) {
self.active = self.top
self.focus = entry.Focus
self.failureMode = defaultFailureMode
self.Register(entry)
self.reporter.BeginStory(reporting.NewStoryReport(entry.Test))
for !self.top.visited() {
self.top.visit(self)
}
self.reporter.EndStory()
}
func newRunner(reporter reporting.Reporter) *runner {
// Top-level is always using a nilReporter
scope := newScope(newRegistration(topLevel, newAction(func() {}, FailureInherits), nil), newNilReporter())
return &runner{
reporter: reporter,
top: scope,
active: scope,
}
}
func (self *runner) Report(result *reporting.AssertionResult) {
self.reporter.Report(result)
if result.Failure != "" && self.failureMode == FailureHalts {
panic(failureHalt)
}
}
func (self *runner) Write(content []byte) (written int, err error) {
return self.reporter.Write(content)
}
func (self *runner) setFailureMode(mode FailureMode) FailureMode {
old := self.failureMode
if mode != FailureInherits {
self.failureMode = mode
}
return old
}
func last(group []string) string {
return group[len(group)-1]
}
const topLevel = "TOP"
const failureHalt = "___FAILURE_HALT___"
//////////////////////// nilReporter /////////////////////////////
type nilReporter struct{}
func (self *nilReporter) BeginStory(story *reporting.StoryReport) {}
func (self *nilReporter) Enter(scope *reporting.ScopeReport) {}
func (self *nilReporter) Report(report *reporting.AssertionResult) {}
func (self *nilReporter) Exit() {}
func (self *nilReporter) EndStory() {}
func (self *nilReporter) Write(p []byte) (int, error) { return len(p), nil }
func newNilReporter() *nilReporter { return &nilReporter{} }

View File

@ -1,116 +0,0 @@
package convey
import (
"fmt"
"strings"
"github.com/smartystreets/goconvey/convey/reporting"
)
type scope struct {
name string
title string
action *action
children map[string]*scope
birthOrder []*scope
child int
resetOrder []string
resets map[string]*action
panicked bool
reporter reporting.Reporter
report *reporting.ScopeReport
}
func (parent *scope) adopt(child *scope) {
i := parent.getChildIndex(child)
if i == -1 {
parent.children[child.name] = child
parent.birthOrder = append(parent.birthOrder, child)
} else {
/* We need to replace the action to retain the closed over variables from
the specific invocation of the parent scope, enabling the enclosing
parent scope to serve as a set-up for the child scope */
parent.birthOrder[i].action = child.action
}
}
func (parent *scope) getChildIndex(child *scope) int {
for i, ordered := range parent.birthOrder {
if ordered.name == child.name && ordered.title == child.title {
return i
}
}
return -1
}
func (self *scope) registerReset(action *action) {
self.resets[action.name] = action
for _, name := range self.resetOrder {
if name == action.name {
return
}
}
self.resetOrder = append(self.resetOrder, action.name)
}
func (self *scope) visited() bool {
return self.panicked || self.child >= len(self.birthOrder)
}
func (parent *scope) visit(runner *runner) {
runner.active = parent
defer parent.exit()
oldMode := runner.setFailureMode(parent.action.failureMode)
defer runner.setFailureMode(oldMode)
parent.reporter.Enter(parent.report)
parent.action.Invoke()
parent.visitNextChild(runner)
parent.cleanup()
}
func (parent *scope) visitNextChild(runner *runner) {
if len(parent.birthOrder) > parent.child {
child := parent.birthOrder[parent.child]
child.visit(runner)
if child.visited() {
parent.child++
}
}
}
func (parent *scope) cleanup() {
for _, name := range parent.resetOrder {
reset := parent.resets[name]
reset.Invoke()
}
}
func (parent *scope) exit() {
if problem := recover(); problem != nil {
if strings.HasPrefix(fmt.Sprintf("%v", problem), extraGoTest) {
panic(problem)
}
if problem != failureHalt {
parent.reporter.Report(reporting.NewErrorReport(problem))
}
parent.panicked = true
}
parent.reporter.Exit()
}
func newScope(entry *registration, reporter reporting.Reporter) *scope {
return &scope{
reporter: reporter,
name: entry.action.name,
title: entry.Situation,
action: entry.action,
children: make(map[string]*scope),
birthOrder: []*scope{},
resetOrder: []string{},
resets: make(map[string]*action),
report: reporting.NewScopeReport(entry.Situation, entry.action.name),
}
}

View File

@ -1,185 +0,0 @@
package convey
import (
"fmt"
"strings"
"testing"
)
func TestMissingTopLevelGoTestReferenceCausesPanic(t *testing.T) {
output := map[string]bool{}
defer expectEqual(t, false, output["good"])
defer requireGoTestReference(t)
Convey("Hi", func() {
output["bad"] = true // this shouldn't happen
})
}
func requireGoTestReference(t *testing.T) {
err := recover()
if err == nil {
t.Error("We should have recovered a panic here (because of a missing *testing.T reference)!")
} else {
expectEqual(t, missingGoTest, err)
}
}
func TestMissingTopLevelGoTestReferenceAfterGoodExample(t *testing.T) {
output := map[string]bool{}
defer func() {
expectEqual(t, true, output["good"])
expectEqual(t, false, output["bad"])
}()
defer requireGoTestReference(t)
Convey("Good example", t, func() {
output["good"] = true
})
Convey("Bad example", func() {
output["bad"] = true // shouldn't happen
})
}
func TestExtraReferencePanics(t *testing.T) {
output := map[string]bool{}
defer func() {
err := recover()
if err == nil {
t.Error("We should have recovered a panic here (because of an extra *testing.T reference)!")
} else if !strings.HasPrefix(fmt.Sprintf("%v", err), extraGoTest) {
t.Error("Should have panicked with the 'extra go test' error!")
}
if output["bad"] {
t.Error("We should NOT have run the bad example!")
}
}()
Convey("Good example", t, func() {
Convey("Bad example - passing in *testing.T a second time!", t, func() {
output["bad"] = true // shouldn't happen
})
})
}
func TestParseRegistrationMissingRequiredElements(t *testing.T) {
defer func() {
if r := recover(); r != nil {
if r != "You must provide a name (string), then a *testing.T (if in outermost scope), an optional FailureMode, and then an action (func())." {
t.Errorf("Incorrect panic message.")
}
}
}()
Convey()
t.Errorf("goTest should have panicked in Convey(...) and then recovered in the defer func().")
}
func TestParseRegistration_MissingNameString(t *testing.T) {
defer func() {
if r := recover(); r != nil {
if r != parseError {
t.Errorf("Incorrect panic message.")
}
}
}()
action := func() {}
Convey(action)
t.Errorf("goTest should have panicked in Convey(...) and then recovered in the defer func().")
}
func TestParseRegistration_MissingActionFunc(t *testing.T) {
defer func() {
if r := recover(); r != nil {
if r != parseError {
t.Errorf("Incorrect panic message: '%s'", r)
}
}
}()
Convey("Hi there", 12345)
t.Errorf("goTest should have panicked in Convey(...) and then recovered in the defer func().")
}
func TestFailureModeParameterButMissing(t *testing.T) {
defer func() {
if r := recover(); r != nil {
if r != parseError {
t.Errorf("Incorrect panic message.")
}
} else {
t.Errorf("Expected panic")
}
}()
prepare()
Convey("Foobar", t, FailureHalts)
}
func TestFailureModeParameterWithAction(t *testing.T) {
prepare()
defer func() {
if r := recover(); r != nil {
t.Errorf("Unexpected panic")
}
}()
Convey("Foobar", t, FailureHalts, func() {})
}
func TestExtraConveyParameters(t *testing.T) {
defer func() {
if r := recover(); r != nil {
if r != parseError {
t.Errorf("Incorrect panic message.")
}
} else {
t.Errorf("Expected panic")
}
}()
prepare()
Convey("Foobar", t, FailureHalts, func() {}, "This is not supposed to be here")
}
func TestExtraConveyParameters2(t *testing.T) {
defer func() {
if r := recover(); r != nil {
if r != parseError {
t.Errorf("Incorrect panic message.")
}
} else {
t.Errorf("Expected panic")
}
}()
prepare()
Convey("Foobar", t, func() {}, "This is not supposed to be here")
}
func TestExtraConveyParameters3(t *testing.T) {
output := prepare()
Convey("A", t, func() {
output += "A "
Convey("B", func() {
output += "B "
}, "This is not supposed to be here")
})
expectEqual(t, "A ", output)
}

View File

@ -121,11 +121,15 @@ v, err = cfg.Section("").Key("BOOL").Bool()
v, err = cfg.Section("").Key("FLOAT64").Float64()
v, err = cfg.Section("").Key("INT").Int()
v, err = cfg.Section("").Key("INT64").Int64()
v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
v, err = cfg.Section("").Key("TIME").Time() // RFC3339
v = cfg.Section("").Key("BOOL").MustBool()
v = cfg.Section("").Key("FLOAT64").MustFloat64()
v = cfg.Section("").Key("INT").MustInt()
v = cfg.Section("").Key("INT64").MustInt64()
v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
v = cfg.Section("").Key("TIME").MustTime() // RFC3339
// Methods start with Must also accept one argument for default value
// when key not found or fail to parse value to given type.
@ -136,6 +140,8 @@ v = cfg.Section("").Key("BOOL").MustBool(true)
v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
v = cfg.Section("").Key("INT").MustInt(10)
v = cfg.Section("").Key("INT64").MustInt64(99)
v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
```
What if my value is three-line long?
@ -170,6 +176,7 @@ v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3})
```
Default value will be presented if value of key is not in candidates you given, and default value does not need be one of candidates.
@ -181,11 +188,12 @@ vals = cfg.Section("").Key("STRINGS").Strings(",")
vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
vals = cfg.Section("").Key("INTS").Ints(",")
vals = cfg.Section("").Key("INT64S").Int64s(",")
vals = cfg.Section("").Key("TIMES").Times(",")
```
### Advanced Usage
## Advanced Usage
#### Recursive Values
### Recursive Values
For all value of keys, there is a special syntax `%(<name>)s`, where `<name>` is the key name in same section or default section, and `%(<name>)s` will be replaced by corresponding value(empty string if key not found). You can use this syntax at most 99 level of recursions.
@ -205,7 +213,7 @@ cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon
cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini
```
#### Parent-child Sections
### Parent-child Sections
You can use `.` in section name to indicate parent-child relationship between two or more sections. If the key not found in the child section, library will try again on its parent section until there is no parent section.
@ -224,7 +232,7 @@ CLONE_URL = https://%(IMPORT_PATH)s
cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
```
#### Auto-increment Key Names
### Auto-increment Key Names
If key name is `-` in data source, then it would be seen as special syntax for auto-increment key name start from 1, and every section is independent on counter.
@ -239,6 +247,83 @@ If key name is `-` in data source, then it would be seen as special syntax for a
cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"}
```
### Map To Struct
Want more objective way to play with INI? Cool.
```ini
Name = Unknwon
age = 21
Male = true
Born = 1993-01-01T20:17:05Z
[Note]
Content = Hi is a good man!
Cities = HangZhou, Boston
```
```go
type Note struct {
Content string
Cities []string
}
type Person struct {
Name string
Age int `ini:"age"`
Male bool
Born time.Time
Note
Created time.Time `ini:"-"`
}
func main() {
cfg, err := ini.Load("path/to/ini")
// ...
p := new(Person)
err = cfg.MapTo(p)
// ...
// Things can be simpler.
err = ini.MapTo(p, "path/to/ini")
// ...
// Just map a section? Fine.
n := new(Note)
err = cfg.Section("Note").MapTo(n)
// ...
}
```
#### Name Mapper
To save your time and make your code cleaner, this library supports [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) between struct field and actual secion and key name.
There are 2 built-in name mappers:
- `AllCapsUnderscore`: it converts to format `ALL_CAPS_UNDERSCORE` then match section or key.
- `TitleUnderscore`: it converts to format `title_underscore` then match section or key.
To use them:
```go
type Info struct{
PackageName string
}
func main() {
err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("packag_name=ini"))
// ...
cfg, err := ini.Load("PACKAGE_NAME=ini")
// ...
info := new(Info)
cfg.NameMapper = ini.AllCapsUnderscore
err = cfg.MapTo(info)
// ...
}
```
## Getting Help
- [API Documentation](https://gowalker.org/gopkg.in/ini.v1)

View File

@ -116,11 +116,15 @@ v, err = cfg.Section("").Key("BOOL").Bool()
v, err = cfg.Section("").Key("FLOAT64").Float64()
v, err = cfg.Section("").Key("INT").Int()
v, err = cfg.Section("").Key("INT64").Int64()
v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
v, err = cfg.Section("").Key("TIME").Time() // RFC3339
v = cfg.Section("").Key("BOOL").MustBool()
v = cfg.Section("").Key("FLOAT64").MustFloat64()
v = cfg.Section("").Key("INT").MustInt()
v = cfg.Section("").Key("INT64").MustInt64()
v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
v = cfg.Section("").Key("TIME").MustTime() // RFC3339
// 由 Must 开头的方法名允许接收一个相同类型的参数来作为默认值,
// 当键不存在或者转换失败时,则会直接返回该默认值。
@ -131,6 +135,8 @@ v = cfg.Section("").Key("BOOL").MustBool(true)
v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
v = cfg.Section("").Key("INT").MustInt(10)
v = cfg.Section("").Key("INT64").MustInt64(99)
v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
```
如果我的值有好多行怎么办?
@ -165,6 +171,7 @@ v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3})
```
如果获取到的值不是候选值的任意一个,则会返回默认值,而默认值不需要是候选值中的一员。
@ -176,6 +183,7 @@ vals = cfg.Section("").Key("STRINGS").Strings(",")
vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
vals = cfg.Section("").Key("INTS").Ints(",")
vals = cfg.Section("").Key("INT64S").Int64s(",")
vals = cfg.Section("").Key("TIMES").Times(",")
```
### 高级用法
@ -234,6 +242,83 @@ cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"}
```
### 映射到结构
想要使用更加面向对象的方式玩转 INI 吗?好主意。
```ini
Name = Unknwon
age = 21
Male = true
Born = 1993-01-01T20:17:05Z
[Note]
Content = Hi is a good man!
Cities = HangZhou, Boston
```
```go
type Note struct {
Content string
Cities []string
}
type Person struct {
Name string
Age int `ini:"age"`
Male bool
Born time.Time
Note
Created time.Time `ini:"-"`
}
func main() {
cfg, err := ini.Load("path/to/ini")
// ...
p := new(Person)
err = cfg.MapTo(p)
// ...
// 一切竟可以如此的简单。
err = ini.MapTo(p, "path/to/ini")
// ...
// 嗯哼?只需要映射一个分区吗?
n := new(Note)
err = cfg.Section("Note").MapTo(n)
// ...
}
```
#### 名称映射器Name Mapper
为了节省您的时间并简化代码,本库支持类型为 [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) 的名称映射器,该映射器负责结构字段名与分区名和键名之间的映射。
目前有 2 款内置的映射器:
- `AllCapsUnderscore`:该映射器将字段名转换至格式 `ALL_CAPS_UNDERSCORE` 后再去匹配分区名和键名。
- `TitleUnderscore`:该映射器将字段名转换至格式 `title_underscore` 后再去匹配分区名和键名。
使用方法:
```go
type Info struct{
PackageName string
}
func main() {
err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("packag_name=ini"))
// ...
cfg, err := ini.Load("PACKAGE_NAME=ini")
// ...
info := new(Info)
cfg.NameMapper = ini.AllCapsUnderscore
err = cfg.MapTo(info)
// ...
}
```
## 获取帮助
- [API 文档](https://gowalker.org/gopkg.in/ini.v1)

View File

@ -27,14 +27,21 @@ import (
"strconv"
"strings"
"sync"
"time"
)
const (
DEFAULT_SECTION = "DEFAULT"
// Maximum allowed depth when recursively substituing variable names.
_DEPTH_VALUES = 99
_VERSION = "1.0.1"
)
func Version() string {
return _VERSION
}
var (
LineBreak = "\n"
@ -157,6 +164,16 @@ func (k *Key) Int64() (int64, error) {
return strconv.ParseInt(k.String(), 10, 64)
}
// TimeFormat parses with given format and returns time.Time type value.
func (k *Key) TimeFormat(format string) (time.Time, error) {
return time.Parse(format, k.String())
}
// Time parses with RFC3339 format and returns time.Time type value.
func (k *Key) Time() (time.Time, error) {
return k.TimeFormat(time.RFC3339)
}
// MustString returns default value if key value is empty.
func (k *Key) MustString(defaultVal string) string {
val := k.String()
@ -179,31 +196,47 @@ func (k *Key) MustBool(defaultVal ...bool) bool {
// MustFloat64 always returns value without error,
// it returns 0.0 if error occurs.
func (k *Key) MustFloat64(defaultVal ...float64) float64 {
value, err := k.Float64()
val, err := k.Float64()
if len(defaultVal) > 0 && err != nil {
return defaultVal[0]
}
return value
return val
}
// MustInt always returns value without error,
// it returns 0 if error occurs.
func (k *Key) MustInt(defaultVal ...int) int {
value, err := k.Int()
val, err := k.Int()
if len(defaultVal) > 0 && err != nil {
return defaultVal[0]
}
return value
return val
}
// MustInt64 always returns value without error,
// it returns 0 if error occurs.
func (k *Key) MustInt64(defaultVal ...int64) int64 {
value, err := k.Int64()
val, err := k.Int64()
if len(defaultVal) > 0 && err != nil {
return defaultVal[0]
}
return value
return val
}
// MustTimeFormat always parses with given format and returns value without error,
// it returns zero value if error occurs.
func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time {
val, err := k.TimeFormat(format)
if len(defaultVal) > 0 && err != nil {
return defaultVal[0]
}
return val
}
// MustTime always parses with RFC3339 format and returns value without error,
// it returns zero value if error occurs.
func (k *Key) MustTime(defaultVal ...time.Time) time.Time {
return k.MustTimeFormat(time.RFC3339, defaultVal...)
}
// In always returns value without error,
@ -218,7 +251,7 @@ func (k *Key) In(defaultVal string, candidates []string) string {
return defaultVal
}
// In always returns value without error,
// InFloat64 always returns value without error,
// it returns default value if error occurs or doesn't fit into candidates.
func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
val := k.MustFloat64()
@ -230,7 +263,7 @@ func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
return defaultVal
}
// In always returns value without error,
// InInt always returns value without error,
// it returns default value if error occurs or doesn't fit into candidates.
func (k *Key) InInt(defaultVal int, candidates []int) int {
val := k.MustInt()
@ -242,7 +275,7 @@ func (k *Key) InInt(defaultVal int, candidates []int) int {
return defaultVal
}
// In always returns value without error,
// InInt64 always returns value without error,
// it returns default value if error occurs or doesn't fit into candidates.
func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
val := k.MustInt64()
@ -254,6 +287,24 @@ func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
return defaultVal
}
// InTimeFormat always parses with given format and returns value without error,
// it returns default value if error occurs or doesn't fit into candidates.
func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
val := k.MustTimeFormat(format)
for _, cand := range candidates {
if val == cand {
return val
}
}
return defaultVal
}
// InTime always parses with RFC3339 format and returns value without error,
// it returns default value if error occurs or doesn't fit into candidates.
func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
}
// Strings returns list of string devide by given delimiter.
func (k *Key) Strings(delim string) []string {
str := k.String()
@ -298,6 +349,21 @@ func (k *Key) Int64s(delim string) []int64 {
return vals
}
// TimesFormat parses with given format and returns list of time.Time devide by given delimiter.
func (k *Key) TimesFormat(format, delim string) []time.Time {
strs := k.Strings(delim)
vals := make([]time.Time, len(strs))
for i := range strs {
vals[i], _ = time.Parse(format, strs[i])
}
return vals
}
// Times parses with RFC3339 format and returns list of time.Time devide by given delimiter.
func (k *Key) Times(delim string) []time.Time {
return k.TimesFormat(time.RFC3339, delim)
}
// SetValue changes key value.
func (k *Key) SetValue(v string) {
k.value = v
@ -446,6 +512,8 @@ type File struct {
// To keep data in order.
sectionList []string
NameMapper
}
// newFile initializes File object with given data sources.
@ -803,6 +871,11 @@ func (f *File) SaveTo(filename string) (err error) {
if _, err = buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
return err
}
} else {
// Write nothing if default section is empty.
if len(sec.keyList) == 0 {
continue
}
}
for _, kname := range sec.keyList {

View File

@ -18,10 +18,17 @@ import (
"fmt"
"strings"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
func Test_Version(t *testing.T) {
Convey("Get version", t, func() {
So(Version(), ShouldEqual, _VERSION)
})
}
const _CONF_DATA = `
; Package name
NAME = ini
@ -57,11 +64,13 @@ STRING = str
BOOL = true
FLOAT64 = 1.25
INT = 10
TIME = 2015-01-01T20:17:05Z
[array]
STRINGS = en, zh, de
FLOAT64S = 1.1, 2.2, 3.3
INTS = 1, 2, 3
TIMES = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z
[note]
@ -206,12 +215,19 @@ func Test_Values(t *testing.T) {
So(err, ShouldBeNil)
So(v4, ShouldEqual, 10)
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
So(err, ShouldBeNil)
v5, err := sec.Key("TIME").Time()
So(err, ShouldBeNil)
So(v5.String(), ShouldEqual, t.String())
Convey("Must get values with type", func() {
So(sec.Key("STRING").MustString("404"), ShouldEqual, "str")
So(sec.Key("BOOL").MustBool(), ShouldBeTrue)
So(sec.Key("FLOAT64").MustFloat64(), ShouldEqual, 1.25)
So(sec.Key("INT").MustInt(), ShouldEqual, 10)
So(sec.Key("INT").MustInt64(), ShouldEqual, 10)
So(sec.Key("TIME").MustTime().String(), ShouldEqual, t.String())
Convey("Must get values with default value", func() {
So(sec.Key("STRING_404").MustString("404"), ShouldEqual, "404")
@ -219,6 +235,10 @@ func Test_Values(t *testing.T) {
So(sec.Key("FLOAT64_404").MustFloat64(2.5), ShouldEqual, 2.5)
So(sec.Key("INT_404").MustInt(15), ShouldEqual, 15)
So(sec.Key("INT_404").MustInt64(15), ShouldEqual, 15)
t, err := time.Parse(time.RFC3339, "2014-01-01T20:17:05Z")
So(err, ShouldBeNil)
So(sec.Key("TIME_404").MustTime(t).String(), ShouldEqual, t.String())
})
})
})
@ -230,11 +250,18 @@ func Test_Values(t *testing.T) {
So(sec.Key("INT").InInt(0, []int{10, 20, 30}), ShouldEqual, 10)
So(sec.Key("INT").InInt64(0, []int64{10, 20, 30}), ShouldEqual, 10)
zt, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
So(err, ShouldBeNil)
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
So(err, ShouldBeNil)
So(sec.Key("TIME").InTime(zt, []time.Time{t, time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
Convey("Get value with candidates and default value", func() {
So(sec.Key("STRING_404").In("str", []string{"str", "arr", "types"}), ShouldEqual, "str")
So(sec.Key("FLOAT64_404").InFloat64(1.25, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25)
So(sec.Key("INT_404").InInt(10, []int{10, 20, 30}), ShouldEqual, 10)
So(sec.Key("INT64_404").InInt64(10, []int64{10, 20, 30}), ShouldEqual, 10)
So(sec.Key("TIME_404").InTime(t, []time.Time{time.Now(), time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
})
})
@ -257,6 +284,13 @@ func Test_Values(t *testing.T) {
for i, v := range []int64{1, 2, 3} {
So(vals3[i], ShouldEqual, v)
}
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
So(err, ShouldBeNil)
vals4 := sec.Key("TIMES").Times(",")
for i, v := range []time.Time{t, t, t} {
So(vals4[i].String(), ShouldEqual, v.String())
}
})
Convey("Get key hash", func() {
@ -270,7 +304,7 @@ func Test_Values(t *testing.T) {
})
Convey("Get key strings", func() {
So(strings.Join(cfg.Section("types").KeyStrings(), ","), ShouldEqual, "STRING,BOOL,FLOAT64,INT")
So(strings.Join(cfg.Section("types").KeyStrings(), ","), ShouldEqual, "STRING,BOOL,FLOAT64,INT,TIME")
})
Convey("Delete a key", func() {
@ -338,6 +372,9 @@ func Test_File_SaveTo(t *testing.T) {
So(err, ShouldBeNil)
So(cfg, ShouldNotBeNil)
cfg.Section("").Key("NAME").Comment = "Package name"
cfg.Section("author").Comment = `Information about package author
# Bio can be written in multiple lines.`
So(cfg.SaveTo("testdata/conf_out.ini"), ShouldBeNil)
})
}

214
Godeps/_workspace/src/gopkg.in/ini.v1/struct.go generated vendored Normal file
View File

@ -0,0 +1,214 @@
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package ini
import (
"errors"
"fmt"
"reflect"
"time"
"unicode"
)
// NameMapper represents a ini tag name mapper.
type NameMapper func(string) string
// Built-in name getters.
var (
// AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE.
AllCapsUnderscore NameMapper = func(raw string) string {
newstr := make([]rune, 0, 10)
for i, chr := range raw {
if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
if i > 0 {
newstr = append(newstr, '_')
}
}
newstr = append(newstr, unicode.ToUpper(chr))
}
return string(newstr)
}
// TitleUnderscore converts to format title_underscore.
TitleUnderscore NameMapper = func(raw string) string {
newstr := make([]rune, 0, 10)
for i, chr := range raw {
if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
if i > 0 {
newstr = append(newstr, '_')
}
chr -= ('A' - 'a')
}
newstr = append(newstr, chr)
}
return string(newstr)
}
)
func (s *Section) parseFieldName(raw, actual string) string {
if len(actual) > 0 {
return actual
}
if s.f.NameMapper != nil {
return s.f.NameMapper(raw)
}
return raw
}
func parseDelim(actual string) string {
if len(actual) > 0 {
return actual
}
return ","
}
var reflectTime = reflect.TypeOf(time.Now()).Kind()
func setWithProperType(kind reflect.Kind, key *Key, field reflect.Value, delim string) error {
switch kind {
case reflect.String:
field.SetString(key.String())
case reflect.Bool:
boolVal, err := key.Bool()
if err != nil {
return err
}
field.SetBool(boolVal)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
intVal, err := key.Int64()
if err != nil {
return err
}
field.SetInt(intVal)
case reflect.Float64:
floatVal, err := key.Float64()
if err != nil {
return err
}
field.SetFloat(floatVal)
case reflectTime:
timeVal, err := key.Time()
if err != nil {
return err
}
field.Set(reflect.ValueOf(timeVal))
case reflect.Slice:
vals := key.Strings(delim)
numVals := len(vals)
if numVals == 0 {
return nil
}
sliceOf := field.Type().Elem().Kind()
var times []time.Time
if sliceOf == reflectTime {
times = key.Times(delim)
}
slice := reflect.MakeSlice(field.Type(), numVals, numVals)
for i := 0; i < numVals; i++ {
switch sliceOf {
case reflectTime:
slice.Index(i).Set(reflect.ValueOf(times[i]))
default:
slice.Index(i).Set(reflect.ValueOf(vals[i]))
}
}
field.Set(slice)
default:
return fmt.Errorf("unsupported type '%s'", kind)
}
return nil
}
func (s *Section) mapTo(val reflect.Value) error {
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
typ := val.Type()
for i := 0; i < typ.NumField(); i++ {
field := val.Field(i)
tpField := typ.Field(i)
tag := tpField.Tag.Get("ini")
if tag == "-" {
continue
}
fieldName := s.parseFieldName(tpField.Name, tag)
if len(fieldName) == 0 || !field.CanSet() {
continue
}
if tpField.Type.Kind() == reflect.Struct {
if sec, err := s.f.GetSection(fieldName); err == nil {
if err = sec.mapTo(field); err != nil {
return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
}
continue
}
} else if tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous {
field.Set(reflect.New(tpField.Type.Elem()))
if sec, err := s.f.GetSection(fieldName); err == nil {
if err = sec.mapTo(field); err != nil {
return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
}
continue
}
}
if key, err := s.GetKey(fieldName); err == nil {
if err = setWithProperType(tpField.Type.Kind(), key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
}
}
}
return nil
}
// MapTo maps section to given struct.
func (s *Section) MapTo(v interface{}) error {
typ := reflect.TypeOf(v)
val := reflect.ValueOf(v)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
val = val.Elem()
} else {
return errors.New("cannot map to non-pointer struct")
}
return s.mapTo(val)
}
// MapTo maps file to given struct.
func (f *File) MapTo(v interface{}) error {
return f.Section("").MapTo(v)
}
// MapTo maps data sources to given struct with name mapper.
func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
cfg, err := Load(source, others...)
if err != nil {
return err
}
cfg.NameMapper = mapper
return cfg.MapTo(v)
}
// MapTo maps data sources to given struct.
func MapTo(v, source interface{}, others ...interface{}) error {
return MapToWithMapper(v, nil, source, others...)
}

183
Godeps/_workspace/src/gopkg.in/ini.v1/struct_test.go generated vendored Normal file
View File

@ -0,0 +1,183 @@
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package ini
import (
"strings"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
type testNested struct {
Cities []string `delim:"|"`
Visits []time.Time
Note string
Unused int `ini:"-"`
}
type testEmbeded struct {
GPA float64
}
type testStruct struct {
Name string `ini:"NAME"`
Age int
Male bool
Money float64
Born time.Time
Others testNested
*testEmbeded `ini:"grade"`
Unused int `ini:"-"`
}
const _CONF_DATA_STRUCT = `
NAME = Unknwon
Age = 21
Male = true
Money = 1.25
Born = 1993-10-07T20:17:05Z
[Others]
Cities = HangZhou|Boston
Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
Note = Hello world!
[grade]
GPA = 2.8
`
type unsupport struct {
Byte byte
}
type unsupport2 struct {
Others struct {
Cities byte
}
}
type unsupport3 struct {
Cities byte
}
type unsupport4 struct {
*unsupport3 `ini:"Others"`
}
type invalidInt struct {
Age int
}
type invalidBool struct {
Male bool
}
type invalidFloat struct {
Money float64
}
type invalidTime struct {
Born time.Time
}
type emptySlice struct {
Cities []string
}
const _INVALID_DATA_CONF_STRUCT = `
Age = age
Male = 123
Money = money
Born = nil
Cities =
`
func Test_Struct(t *testing.T) {
Convey("Map file to struct", t, func() {
ts := new(testStruct)
So(MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil)
So(ts.Name, ShouldEqual, "Unknwon")
So(ts.Age, ShouldEqual, 21)
So(ts.Male, ShouldBeTrue)
So(ts.Money, ShouldEqual, 1.25)
t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
So(err, ShouldBeNil)
So(ts.Born.String(), ShouldEqual, t.String())
So(strings.Join(ts.Others.Cities, ","), ShouldEqual, "HangZhou,Boston")
So(ts.Others.Visits[0].String(), ShouldEqual, t.String())
So(ts.Others.Note, ShouldEqual, "Hello world!")
So(ts.testEmbeded.GPA, ShouldEqual, 2.8)
})
Convey("Map to non-pointer struct", t, func() {
cfg, err := Load([]byte(_CONF_DATA_STRUCT))
So(err, ShouldBeNil)
So(cfg, ShouldNotBeNil)
So(cfg.MapTo(testStruct{}), ShouldNotBeNil)
})
Convey("Map to unsupported type", t, func() {
cfg, err := Load([]byte(_CONF_DATA_STRUCT))
So(err, ShouldBeNil)
So(cfg, ShouldNotBeNil)
cfg.NameMapper = func(raw string) string {
if raw == "Byte" {
return "NAME"
}
return raw
}
So(cfg.MapTo(&unsupport{}), ShouldNotBeNil)
So(cfg.MapTo(&unsupport2{}), ShouldNotBeNil)
So(cfg.MapTo(&unsupport4{}), ShouldNotBeNil)
})
Convey("Map from invalid data source", t, func() {
So(MapTo(&testStruct{}, "hi"), ShouldNotBeNil)
})
Convey("Map to wrong types", t, func() {
So(MapTo(&invalidInt{}, []byte(_INVALID_DATA_CONF_STRUCT)), ShouldNotBeNil)
So(MapTo(&invalidBool{}, []byte(_INVALID_DATA_CONF_STRUCT)), ShouldNotBeNil)
So(MapTo(&invalidFloat{}, []byte(_INVALID_DATA_CONF_STRUCT)), ShouldNotBeNil)
So(MapTo(&invalidTime{}, []byte(_INVALID_DATA_CONF_STRUCT)), ShouldNotBeNil)
So(MapTo(&emptySlice{}, []byte(_INVALID_DATA_CONF_STRUCT)), ShouldBeNil)
})
}
type testMapper struct {
PackageName string
}
func Test_NameGetter(t *testing.T) {
Convey("Test name mappers", t, func() {
So(MapToWithMapper(&testMapper{}, TitleUnderscore, []byte("packag_name=ini")), ShouldBeNil)
cfg, err := Load([]byte("PACKAGE_NAME=ini"))
So(err, ShouldBeNil)
So(cfg, ShouldNotBeNil)
cfg.NameMapper = AllCapsUnderscore
tg := new(testMapper)
So(cfg.MapTo(tg), ShouldBeNil)
So(tg.PackageName, ShouldEqual, "ini")
})
}