diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 4bc0c70bf6b..c1df9116a4a 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -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" } ] } diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/.gitignore b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/.gitignore new file mode 100644 index 00000000000..ba8e0cb3aa3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +Icon? +ehthumbs.db +Thumbs.db diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/.travis.yml b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/.travis.yml new file mode 100644 index 00000000000..dd29a75803f --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/.travis.yml @@ -0,0 +1,9 @@ +language: go +go: + - 1.1 + - 1.2 + - 1.3 + - tip + +before_script: + - mysql -e 'create database gotest;' diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/AUTHORS b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/AUTHORS new file mode 100644 index 00000000000..f0b07024689 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/AUTHORS @@ -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 +# The email address is not required for organizations. +# Please keep the list sorted. + + +# Individual Persons + +Aaron Hopkins +Arne Hormann +Carlos Nieto +DisposaBoy +Frederick Mayle +Gustavo Kristic +Hanno Braun +Henri Yandell +James Harr +Jian Zhen +Julien Schmidt +Leonardo YongUk Kim +Lucas Liu +Luke Scott +Michael Woolnough +Nicola Peduzzi +Xiaobing Jiang +Xiuming Chen + +# Organizations + +Barracuda Networks, Inc. +Google Inc. diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/CHANGELOG.md b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/CHANGELOG.md new file mode 100644 index 00000000000..9bb92c1cd5f --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/CHANGELOG.md @@ -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 diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/CONTRIBUTING.md b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/CONTRIBUTING.md new file mode 100644 index 00000000000..f87c19824cc --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/CONTRIBUTING.md @@ -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. diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/LICENSE b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/LICENSE new file mode 100644 index 00000000000..14e2f777f6c --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/LICENSE @@ -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. diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/README.md b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/README.md new file mode 100644 index 00000000000..6a2b61e0e64 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/README.md @@ -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&...¶mN=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: +Default: none +``` + +Sets the charset used for client-server interaction (`"SET NAMES "`). 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: +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: +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, +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="` + * `time_zone`: `"SET time_zone="` + * [`tx_isolation`](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation): `"SET tx_isolation="` + * `param`: `"SET ="` + +*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::` 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") + diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/appengine.go b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/appengine.go new file mode 100644 index 00000000000..565614eef7f --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/appengine.go @@ -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) +} diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/benchmark_test.go b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/benchmark_test.go new file mode 100644 index 00000000000..d72a4183fb7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/benchmark_test.go @@ -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() + } +} diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/buffer.go b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/buffer.go new file mode 100644 index 00000000000..509ce89e468 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/buffer.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/collations.go b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/collations.go new file mode 100644 index 00000000000..aabe0055d7e --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/collations.go @@ -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, +} diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/connection.go b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/connection.go new file mode 100644 index 00000000000..67d3dbee8d5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/connection.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/const.go b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/const.go new file mode 100644 index 00000000000..5fcc3e98b12 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/const.go @@ -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 +) diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/driver.go b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/driver.go new file mode 100644 index 00000000000..c0375fe18a3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/driver.go @@ -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{}) +} diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/driver_test.go b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/driver_test.go new file mode 100644 index 00000000000..a52cc5cd00e --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/driver_test.go @@ -0,0 +1,1540 @@ +// 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 ( + "crypto/tls" + "database/sql" + "database/sql/driver" + "fmt" + "io" + "io/ioutil" + "net" + "net/url" + "os" + "strings" + "sync" + "sync/atomic" + "testing" + "time" +) + +var ( + user string + pass string + prot string + addr string + dbname string + dsn string + netAddr string + available bool +) + +var ( + tDate = time.Date(2012, 6, 14, 0, 0, 0, 0, time.UTC) + sDate = "2012-06-14" + tDateTime = time.Date(2011, 11, 20, 21, 27, 37, 0, time.UTC) + sDateTime = "2011-11-20 21:27:37" + tDate0 = time.Time{} + sDate0 = "0000-00-00" + sDateTime0 = "0000-00-00 00:00:00" +) + +// See https://github.com/go-sql-driver/mysql/wiki/Testing +func init() { + // get environment variables + env := func(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue + } + user = env("MYSQL_TEST_USER", "root") + pass = env("MYSQL_TEST_PASS", "") + prot = env("MYSQL_TEST_PROT", "tcp") + addr = env("MYSQL_TEST_ADDR", "localhost:3306") + dbname = env("MYSQL_TEST_DBNAME", "gotest") + netAddr = fmt.Sprintf("%s(%s)", prot, addr) + dsn = fmt.Sprintf("%s:%s@%s/%s?timeout=30s&strict=true", user, pass, netAddr, dbname) + c, err := net.Dial(prot, addr) + if err == nil { + available = true + c.Close() + } +} + +type DBTest struct { + *testing.T + db *sql.DB +} + +func runTests(t *testing.T, dsn string, tests ...func(dbt *DBTest)) { + if !available { + t.Skipf("MySQL-Server not running on %s", netAddr) + } + + db, err := sql.Open("mysql", dsn) + if err != nil { + t.Fatalf("Error connecting: %s", err.Error()) + } + defer db.Close() + + db.Exec("DROP TABLE IF EXISTS test") + + dbt := &DBTest{t, db} + for _, test := range tests { + test(dbt) + dbt.db.Exec("DROP TABLE IF EXISTS test") + } +} + +func (dbt *DBTest) fail(method, query string, err error) { + if len(query) > 300 { + query = "[query too large to print]" + } + dbt.Fatalf("Error on %s %s: %s", method, query, err.Error()) +} + +func (dbt *DBTest) mustExec(query string, args ...interface{}) (res sql.Result) { + res, err := dbt.db.Exec(query, args...) + if err != nil { + dbt.fail("Exec", query, err) + } + return res +} + +func (dbt *DBTest) mustQuery(query string, args ...interface{}) (rows *sql.Rows) { + rows, err := dbt.db.Query(query, args...) + if err != nil { + dbt.fail("Query", query, err) + } + return rows +} + +func TestEmptyQuery(t *testing.T) { + runTests(t, dsn, func(dbt *DBTest) { + // just a comment, no query + rows := dbt.mustQuery("--") + // will hang before #255 + if rows.Next() { + dbt.Errorf("Next on rows must be false") + } + }) +} + +func TestCRUD(t *testing.T) { + runTests(t, dsn, func(dbt *DBTest) { + // Create Table + dbt.mustExec("CREATE TABLE test (value BOOL)") + + // Test for unexpected data + var out bool + rows := dbt.mustQuery("SELECT * FROM test") + if rows.Next() { + dbt.Error("unexpected data in empty table") + } + + // Create Data + res := dbt.mustExec("INSERT INTO test VALUES (1)") + count, err := res.RowsAffected() + if err != nil { + dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error()) + } + if count != 1 { + dbt.Fatalf("Expected 1 affected row, got %d", count) + } + + id, err := res.LastInsertId() + if err != nil { + dbt.Fatalf("res.LastInsertId() returned error: %s", err.Error()) + } + if id != 0 { + dbt.Fatalf("Expected InsertID 0, got %d", id) + } + + // Read + rows = dbt.mustQuery("SELECT value FROM test") + if rows.Next() { + rows.Scan(&out) + if true != out { + dbt.Errorf("true != %t", out) + } + + if rows.Next() { + dbt.Error("unexpected data") + } + } else { + dbt.Error("no data") + } + + // Update + res = dbt.mustExec("UPDATE test SET value = ? WHERE value = ?", false, true) + count, err = res.RowsAffected() + if err != nil { + dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error()) + } + if count != 1 { + dbt.Fatalf("Expected 1 affected row, got %d", count) + } + + // Check Update + rows = dbt.mustQuery("SELECT value FROM test") + if rows.Next() { + rows.Scan(&out) + if false != out { + dbt.Errorf("false != %t", out) + } + + if rows.Next() { + dbt.Error("unexpected data") + } + } else { + dbt.Error("no data") + } + + // Delete + res = dbt.mustExec("DELETE FROM test WHERE value = ?", false) + count, err = res.RowsAffected() + if err != nil { + dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error()) + } + if count != 1 { + dbt.Fatalf("Expected 1 affected row, got %d", count) + } + + // Check for unexpected rows + res = dbt.mustExec("DELETE FROM test") + count, err = res.RowsAffected() + if err != nil { + dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error()) + } + if count != 0 { + dbt.Fatalf("Expected 0 affected row, got %d", count) + } + }) +} + +func TestInt(t *testing.T) { + runTests(t, dsn, func(dbt *DBTest) { + types := [5]string{"TINYINT", "SMALLINT", "MEDIUMINT", "INT", "BIGINT"} + in := int64(42) + var out int64 + var rows *sql.Rows + + // SIGNED + for _, v := range types { + dbt.mustExec("CREATE TABLE test (value " + v + ")") + + dbt.mustExec("INSERT INTO test VALUES (?)", in) + + rows = dbt.mustQuery("SELECT value FROM test") + if rows.Next() { + rows.Scan(&out) + if in != out { + dbt.Errorf("%s: %d != %d", v, in, out) + } + } else { + dbt.Errorf("%s: no data", v) + } + + dbt.mustExec("DROP TABLE IF EXISTS test") + } + + // UNSIGNED ZEROFILL + for _, v := range types { + dbt.mustExec("CREATE TABLE test (value " + v + " ZEROFILL)") + + dbt.mustExec("INSERT INTO test VALUES (?)", in) + + rows = dbt.mustQuery("SELECT value FROM test") + if rows.Next() { + rows.Scan(&out) + if in != out { + dbt.Errorf("%s ZEROFILL: %d != %d", v, in, out) + } + } else { + dbt.Errorf("%s ZEROFILL: no data", v) + } + + dbt.mustExec("DROP TABLE IF EXISTS test") + } + }) +} + +func TestFloat(t *testing.T) { + runTests(t, dsn, func(dbt *DBTest) { + types := [2]string{"FLOAT", "DOUBLE"} + in := float32(42.23) + var out float32 + var rows *sql.Rows + for _, v := range types { + dbt.mustExec("CREATE TABLE test (value " + v + ")") + dbt.mustExec("INSERT INTO test VALUES (?)", in) + rows = dbt.mustQuery("SELECT value FROM test") + if rows.Next() { + rows.Scan(&out) + if in != out { + dbt.Errorf("%s: %g != %g", v, in, out) + } + } else { + dbt.Errorf("%s: no data", v) + } + dbt.mustExec("DROP TABLE IF EXISTS test") + } + }) +} + +func TestString(t *testing.T) { + runTests(t, dsn, func(dbt *DBTest) { + types := [6]string{"CHAR(255)", "VARCHAR(255)", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT"} + in := "κόσμε üöäßñóùéàâÿœ'îë Árvíztűrő いろはにほへとちりぬるを イロハニホヘト דג סקרן чащах น่าฟังเอย" + var out string + var rows *sql.Rows + + for _, v := range types { + dbt.mustExec("CREATE TABLE test (value " + v + ") CHARACTER SET utf8") + + dbt.mustExec("INSERT INTO test VALUES (?)", in) + + rows = dbt.mustQuery("SELECT value FROM test") + if rows.Next() { + rows.Scan(&out) + if in != out { + dbt.Errorf("%s: %s != %s", v, in, out) + } + } else { + dbt.Errorf("%s: no data", v) + } + + dbt.mustExec("DROP TABLE IF EXISTS test") + } + + // BLOB + dbt.mustExec("CREATE TABLE test (id int, value BLOB) CHARACTER SET utf8") + + id := 2 + in = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " + + "sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, " + + "sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. " + + "Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. " + + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " + + "sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, " + + "sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. " + + "Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet." + dbt.mustExec("INSERT INTO test VALUES (?, ?)", id, in) + + err := dbt.db.QueryRow("SELECT value FROM test WHERE id = ?", id).Scan(&out) + if err != nil { + dbt.Fatalf("Error on BLOB-Query: %s", err.Error()) + } else if out != in { + dbt.Errorf("BLOB: %s != %s", in, out) + } + }) +} + +type timeTests struct { + dbtype string + tlayout string + tests []timeTest +} + +type timeTest struct { + s string // leading "!": do not use t as value in queries + t time.Time +} + +type timeMode byte + +func (t timeMode) String() string { + switch t { + case binaryString: + return "binary:string" + case binaryTime: + return "binary:time.Time" + case textString: + return "text:string" + } + panic("unsupported timeMode") +} + +func (t timeMode) Binary() bool { + switch t { + case binaryString, binaryTime: + return true + } + return false +} + +const ( + binaryString timeMode = iota + binaryTime + textString +) + +func (t timeTest) genQuery(dbtype string, mode timeMode) string { + var inner string + if mode.Binary() { + inner = "?" + } else { + inner = `"%s"` + } + return `SELECT cast(` + inner + ` as ` + dbtype + `)` +} + +func (t timeTest) run(dbt *DBTest, dbtype, tlayout string, mode timeMode) { + var rows *sql.Rows + query := t.genQuery(dbtype, mode) + switch mode { + case binaryString: + rows = dbt.mustQuery(query, t.s) + case binaryTime: + rows = dbt.mustQuery(query, t.t) + case textString: + query = fmt.Sprintf(query, t.s) + rows = dbt.mustQuery(query) + default: + panic("unsupported mode") + } + defer rows.Close() + var err error + if !rows.Next() { + err = rows.Err() + if err == nil { + err = fmt.Errorf("no data") + } + dbt.Errorf("%s [%s]: %s", dbtype, mode, err) + return + } + var dst interface{} + err = rows.Scan(&dst) + if err != nil { + dbt.Errorf("%s [%s]: %s", dbtype, mode, err) + return + } + switch val := dst.(type) { + case []uint8: + str := string(val) + if str == t.s { + return + } + if mode.Binary() && dbtype == "DATETIME" && len(str) == 26 && str[:19] == t.s { + // a fix mainly for TravisCI: + // accept full microsecond resolution in result for DATETIME columns + // where the binary protocol was used + return + } + dbt.Errorf("%s [%s] to string: expected %q, got %q", + dbtype, mode, + t.s, str, + ) + case time.Time: + if val == t.t { + return + } + dbt.Errorf("%s [%s] to string: expected %q, got %q", + dbtype, mode, + t.s, val.Format(tlayout), + ) + default: + fmt.Printf("%#v\n", []interface{}{dbtype, tlayout, mode, t.s, t.t}) + dbt.Errorf("%s [%s]: unhandled type %T (is '%v')", + dbtype, mode, + val, val, + ) + } +} + +func TestDateTime(t *testing.T) { + afterTime := func(t time.Time, d string) time.Time { + dur, err := time.ParseDuration(d) + if err != nil { + panic(err) + } + return t.Add(dur) + } + // NOTE: MySQL rounds DATETIME(x) up - but that's not included in the tests + format := "2006-01-02 15:04:05.999999" + t0 := time.Time{} + tstr0 := "0000-00-00 00:00:00.000000" + testcases := []timeTests{ + {"DATE", format[:10], []timeTest{ + {t: time.Date(2011, 11, 20, 0, 0, 0, 0, time.UTC)}, + {t: t0, s: tstr0[:10]}, + }}, + {"DATETIME", format[:19], []timeTest{ + {t: time.Date(2011, 11, 20, 21, 27, 37, 0, time.UTC)}, + {t: t0, s: tstr0[:19]}, + }}, + {"DATETIME(0)", format[:21], []timeTest{ + {t: time.Date(2011, 11, 20, 21, 27, 37, 0, time.UTC)}, + {t: t0, s: tstr0[:19]}, + }}, + {"DATETIME(1)", format[:21], []timeTest{ + {t: time.Date(2011, 11, 20, 21, 27, 37, 100000000, time.UTC)}, + {t: t0, s: tstr0[:21]}, + }}, + {"DATETIME(6)", format, []timeTest{ + {t: time.Date(2011, 11, 20, 21, 27, 37, 123456000, time.UTC)}, + {t: t0, s: tstr0}, + }}, + {"TIME", format[11:19], []timeTest{ + {t: afterTime(t0, "12345s")}, + {s: "!-12:34:56"}, + {s: "!-838:59:59"}, + {s: "!838:59:59"}, + {t: t0, s: tstr0[11:19]}, + }}, + {"TIME(0)", format[11:19], []timeTest{ + {t: afterTime(t0, "12345s")}, + {s: "!-12:34:56"}, + {s: "!-838:59:59"}, + {s: "!838:59:59"}, + {t: t0, s: tstr0[11:19]}, + }}, + {"TIME(1)", format[11:21], []timeTest{ + {t: afterTime(t0, "12345600ms")}, + {s: "!-12:34:56.7"}, + {s: "!-838:59:58.9"}, + {s: "!838:59:58.9"}, + {t: t0, s: tstr0[11:21]}, + }}, + {"TIME(6)", format[11:], []timeTest{ + {t: afterTime(t0, "1234567890123000ns")}, + {s: "!-12:34:56.789012"}, + {s: "!-838:59:58.999999"}, + {s: "!838:59:58.999999"}, + {t: t0, s: tstr0[11:]}, + }}, + } + dsns := []string{ + dsn + "&parseTime=true", + dsn + "&parseTime=false", + } + for _, testdsn := range dsns { + runTests(t, testdsn, func(dbt *DBTest) { + microsecsSupported := false + zeroDateSupported := false + var rows *sql.Rows + var err error + rows, err = dbt.db.Query(`SELECT cast("00:00:00.1" as TIME(1)) = "00:00:00.1"`) + if err == nil { + rows.Scan(µsecsSupported) + rows.Close() + } + rows, err = dbt.db.Query(`SELECT cast("0000-00-00" as DATE) = "0000-00-00"`) + if err == nil { + rows.Scan(&zeroDateSupported) + rows.Close() + } + for _, setups := range testcases { + if t := setups.dbtype; !microsecsSupported && t[len(t)-1:] == ")" { + // skip fractional second tests if unsupported by server + continue + } + for _, setup := range setups.tests { + allowBinTime := true + if setup.s == "" { + // fill time string whereever Go can reliable produce it + setup.s = setup.t.Format(setups.tlayout) + } else if setup.s[0] == '!' { + // skip tests using setup.t as source in queries + allowBinTime = false + // fix setup.s - remove the "!" + setup.s = setup.s[1:] + } + if !zeroDateSupported && setup.s == tstr0[:len(setup.s)] { + // skip disallowed 0000-00-00 date + continue + } + setup.run(dbt, setups.dbtype, setups.tlayout, textString) + setup.run(dbt, setups.dbtype, setups.tlayout, binaryString) + if allowBinTime { + setup.run(dbt, setups.dbtype, setups.tlayout, binaryTime) + } + } + } + }) + } +} + +func TestTimestampMicros(t *testing.T) { + format := "2006-01-02 15:04:05.999999" + f0 := format[:19] + f1 := format[:21] + f6 := format[:26] + runTests(t, dsn, func(dbt *DBTest) { + // check if microseconds are supported. + // Do not use timestamp(x) for that check - before 5.5.6, x would mean display width + // and not precision. + // Se last paragraph at http://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html + microsecsSupported := false + if rows, err := dbt.db.Query(`SELECT cast("00:00:00.1" as TIME(1)) = "00:00:00.1"`); err == nil { + rows.Scan(µsecsSupported) + rows.Close() + } + if !microsecsSupported { + // skip test + return + } + _, err := dbt.db.Exec(` + CREATE TABLE test ( + value0 TIMESTAMP NOT NULL DEFAULT '` + f0 + `', + value1 TIMESTAMP(1) NOT NULL DEFAULT '` + f1 + `', + value6 TIMESTAMP(6) NOT NULL DEFAULT '` + f6 + `' + )`, + ) + if err != nil { + dbt.Error(err) + } + defer dbt.mustExec("DROP TABLE IF EXISTS test") + dbt.mustExec("INSERT INTO test SET value0=?, value1=?, value6=?", f0, f1, f6) + var res0, res1, res6 string + rows := dbt.mustQuery("SELECT * FROM test") + if !rows.Next() { + dbt.Errorf("test contained no selectable values") + } + err = rows.Scan(&res0, &res1, &res6) + if err != nil { + dbt.Error(err) + } + if res0 != f0 { + dbt.Errorf("expected %q, got %q", f0, res0) + } + if res1 != f1 { + dbt.Errorf("expected %q, got %q", f1, res1) + } + if res6 != f6 { + dbt.Errorf("expected %q, got %q", f6, res6) + } + }) +} + +func TestNULL(t *testing.T) { + runTests(t, dsn, func(dbt *DBTest) { + nullStmt, err := dbt.db.Prepare("SELECT NULL") + if err != nil { + dbt.Fatal(err) + } + defer nullStmt.Close() + + nonNullStmt, err := dbt.db.Prepare("SELECT 1") + if err != nil { + dbt.Fatal(err) + } + defer nonNullStmt.Close() + + // NullBool + var nb sql.NullBool + // Invalid + if err = nullStmt.QueryRow().Scan(&nb); err != nil { + dbt.Fatal(err) + } + if nb.Valid { + dbt.Error("Valid NullBool which should be invalid") + } + // Valid + if err = nonNullStmt.QueryRow().Scan(&nb); err != nil { + dbt.Fatal(err) + } + if !nb.Valid { + dbt.Error("Invalid NullBool which should be valid") + } else if nb.Bool != true { + dbt.Errorf("Unexpected NullBool value: %t (should be true)", nb.Bool) + } + + // NullFloat64 + var nf sql.NullFloat64 + // Invalid + if err = nullStmt.QueryRow().Scan(&nf); err != nil { + dbt.Fatal(err) + } + if nf.Valid { + dbt.Error("Valid NullFloat64 which should be invalid") + } + // Valid + if err = nonNullStmt.QueryRow().Scan(&nf); err != nil { + dbt.Fatal(err) + } + if !nf.Valid { + dbt.Error("Invalid NullFloat64 which should be valid") + } else if nf.Float64 != float64(1) { + dbt.Errorf("Unexpected NullFloat64 value: %f (should be 1.0)", nf.Float64) + } + + // NullInt64 + var ni sql.NullInt64 + // Invalid + if err = nullStmt.QueryRow().Scan(&ni); err != nil { + dbt.Fatal(err) + } + if ni.Valid { + dbt.Error("Valid NullInt64 which should be invalid") + } + // Valid + if err = nonNullStmt.QueryRow().Scan(&ni); err != nil { + dbt.Fatal(err) + } + if !ni.Valid { + dbt.Error("Invalid NullInt64 which should be valid") + } else if ni.Int64 != int64(1) { + dbt.Errorf("Unexpected NullInt64 value: %d (should be 1)", ni.Int64) + } + + // NullString + var ns sql.NullString + // Invalid + if err = nullStmt.QueryRow().Scan(&ns); err != nil { + dbt.Fatal(err) + } + if ns.Valid { + dbt.Error("Valid NullString which should be invalid") + } + // Valid + if err = nonNullStmt.QueryRow().Scan(&ns); err != nil { + dbt.Fatal(err) + } + if !ns.Valid { + dbt.Error("Invalid NullString which should be valid") + } else if ns.String != `1` { + dbt.Error("Unexpected NullString value:" + ns.String + " (should be `1`)") + } + + // nil-bytes + var b []byte + // Read nil + if err = nullStmt.QueryRow().Scan(&b); err != nil { + dbt.Fatal(err) + } + if b != nil { + dbt.Error("Non-nil []byte wich should be nil") + } + // Read non-nil + if err = nonNullStmt.QueryRow().Scan(&b); err != nil { + dbt.Fatal(err) + } + if b == nil { + dbt.Error("Nil []byte wich should be non-nil") + } + // Insert nil + b = nil + success := false + if err = dbt.db.QueryRow("SELECT ? IS NULL", b).Scan(&success); err != nil { + dbt.Fatal(err) + } + if !success { + dbt.Error("Inserting []byte(nil) as NULL failed") + } + // Check input==output with input==nil + b = nil + if err = dbt.db.QueryRow("SELECT ?", b).Scan(&b); err != nil { + dbt.Fatal(err) + } + if b != nil { + dbt.Error("Non-nil echo from nil input") + } + // Check input==output with input!=nil + b = []byte("") + if err = dbt.db.QueryRow("SELECT ?", b).Scan(&b); err != nil { + dbt.Fatal(err) + } + if b == nil { + dbt.Error("nil echo from non-nil input") + } + + // Insert NULL + dbt.mustExec("CREATE TABLE test (dummmy1 int, value int, dummy2 int)") + + dbt.mustExec("INSERT INTO test VALUES (?, ?, ?)", 1, nil, 2) + + var out interface{} + rows := dbt.mustQuery("SELECT * FROM test") + if rows.Next() { + rows.Scan(&out) + if out != nil { + dbt.Errorf("%v != nil", out) + } + } else { + dbt.Error("no data") + } + }) +} + +func TestLongData(t *testing.T) { + runTests(t, dsn, func(dbt *DBTest) { + var maxAllowedPacketSize int + err := dbt.db.QueryRow("select @@max_allowed_packet").Scan(&maxAllowedPacketSize) + if err != nil { + dbt.Fatal(err) + } + maxAllowedPacketSize-- + + // don't get too ambitious + if maxAllowedPacketSize > 1<<25 { + maxAllowedPacketSize = 1 << 25 + } + + dbt.mustExec("CREATE TABLE test (value LONGBLOB)") + + in := strings.Repeat(`a`, maxAllowedPacketSize+1) + var out string + var rows *sql.Rows + + // Long text data + const nonDataQueryLen = 28 // length query w/o value + inS := in[:maxAllowedPacketSize-nonDataQueryLen] + dbt.mustExec("INSERT INTO test VALUES('" + inS + "')") + rows = dbt.mustQuery("SELECT value FROM test") + if rows.Next() { + rows.Scan(&out) + if inS != out { + dbt.Fatalf("LONGBLOB: length in: %d, length out: %d", len(inS), len(out)) + } + if rows.Next() { + dbt.Error("LONGBLOB: unexpexted row") + } + } else { + dbt.Fatalf("LONGBLOB: no data") + } + + // Empty table + dbt.mustExec("TRUNCATE TABLE test") + + // Long binary data + dbt.mustExec("INSERT INTO test VALUES(?)", in) + rows = dbt.mustQuery("SELECT value FROM test WHERE 1=?", 1) + if rows.Next() { + rows.Scan(&out) + if in != out { + dbt.Fatalf("LONGBLOB: length in: %d, length out: %d", len(in), len(out)) + } + if rows.Next() { + dbt.Error("LONGBLOB: unexpexted row") + } + } else { + if err = rows.Err(); err != nil { + dbt.Fatalf("LONGBLOB: no data (err: %s)", err.Error()) + } else { + dbt.Fatal("LONGBLOB: no data (err: )") + } + } + }) +} + +func TestLoadData(t *testing.T) { + runTests(t, dsn, func(dbt *DBTest) { + verifyLoadDataResult := func() { + rows, err := dbt.db.Query("SELECT * FROM test") + if err != nil { + dbt.Fatal(err.Error()) + } + + i := 0 + values := [4]string{ + "a string", + "a string containing a \t", + "a string containing a \n", + "a string containing both \t\n", + } + + var id int + var value string + + for rows.Next() { + i++ + err = rows.Scan(&id, &value) + if err != nil { + dbt.Fatal(err.Error()) + } + if i != id { + dbt.Fatalf("%d != %d", i, id) + } + if values[i-1] != value { + dbt.Fatalf("%s != %s", values[i-1], value) + } + } + err = rows.Err() + if err != nil { + dbt.Fatal(err.Error()) + } + + if i != 4 { + dbt.Fatalf("Rows count mismatch. Got %d, want 4", i) + } + } + file, err := ioutil.TempFile("", "gotest") + defer os.Remove(file.Name()) + if err != nil { + dbt.Fatal(err) + } + file.WriteString("1\ta string\n2\ta string containing a \\t\n3\ta string containing a \\n\n4\ta string containing both \\t\\n\n") + file.Close() + + dbt.db.Exec("DROP TABLE IF EXISTS test") + dbt.mustExec("CREATE TABLE test (id INT NOT NULL PRIMARY KEY, value TEXT NOT NULL) CHARACTER SET utf8") + + // Local File + RegisterLocalFile(file.Name()) + dbt.mustExec(fmt.Sprintf("LOAD DATA LOCAL INFILE '%q' INTO TABLE test", file.Name())) + verifyLoadDataResult() + // negative test + _, err = dbt.db.Exec("LOAD DATA LOCAL INFILE 'doesnotexist' INTO TABLE test") + if err == nil { + dbt.Fatal("Load non-existent file didn't fail") + } else if err.Error() != "Local File 'doesnotexist' is not registered. Use the DSN parameter 'allowAllFiles=true' to allow all files" { + dbt.Fatal(err.Error()) + } + + // Empty table + dbt.mustExec("TRUNCATE TABLE test") + + // Reader + RegisterReaderHandler("test", func() io.Reader { + file, err = os.Open(file.Name()) + if err != nil { + dbt.Fatal(err) + } + return file + }) + dbt.mustExec("LOAD DATA LOCAL INFILE 'Reader::test' INTO TABLE test") + verifyLoadDataResult() + // negative test + _, err = dbt.db.Exec("LOAD DATA LOCAL INFILE 'Reader::doesnotexist' INTO TABLE test") + if err == nil { + dbt.Fatal("Load non-existent Reader didn't fail") + } else if err.Error() != "Reader 'doesnotexist' is not registered" { + dbt.Fatal(err.Error()) + } + }) +} + +func TestFoundRows(t *testing.T) { + runTests(t, dsn, func(dbt *DBTest) { + dbt.mustExec("CREATE TABLE test (id INT NOT NULL ,data INT NOT NULL)") + dbt.mustExec("INSERT INTO test (id, data) VALUES (0, 0),(0, 0),(1, 0),(1, 0),(1, 1)") + + res := dbt.mustExec("UPDATE test SET data = 1 WHERE id = 0") + count, err := res.RowsAffected() + if err != nil { + dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error()) + } + if count != 2 { + dbt.Fatalf("Expected 2 affected rows, got %d", count) + } + res = dbt.mustExec("UPDATE test SET data = 1 WHERE id = 1") + count, err = res.RowsAffected() + if err != nil { + dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error()) + } + if count != 2 { + dbt.Fatalf("Expected 2 affected rows, got %d", count) + } + }) + runTests(t, dsn+"&clientFoundRows=true", func(dbt *DBTest) { + dbt.mustExec("CREATE TABLE test (id INT NOT NULL ,data INT NOT NULL)") + dbt.mustExec("INSERT INTO test (id, data) VALUES (0, 0),(0, 0),(1, 0),(1, 0),(1, 1)") + + res := dbt.mustExec("UPDATE test SET data = 1 WHERE id = 0") + count, err := res.RowsAffected() + if err != nil { + dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error()) + } + if count != 2 { + dbt.Fatalf("Expected 2 matched rows, got %d", count) + } + res = dbt.mustExec("UPDATE test SET data = 1 WHERE id = 1") + count, err = res.RowsAffected() + if err != nil { + dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error()) + } + if count != 3 { + dbt.Fatalf("Expected 3 matched rows, got %d", count) + } + }) +} + +func TestStrict(t *testing.T) { + // ALLOW_INVALID_DATES to get rid of stricter modes - we want to test for warnings, not errors + relaxedDsn := dsn + "&sql_mode=ALLOW_INVALID_DATES" + // make sure the MySQL version is recent enough with a separate connection + // before running the test + conn, err := MySQLDriver{}.Open(relaxedDsn) + if conn != nil { + conn.Close() + } + if me, ok := err.(*MySQLError); ok && me.Number == 1231 { + // Error 1231: Variable 'sql_mode' can't be set to the value of 'ALLOW_INVALID_DATES' + // => skip test, MySQL server version is too old + return + } + runTests(t, relaxedDsn, func(dbt *DBTest) { + dbt.mustExec("CREATE TABLE test (a TINYINT NOT NULL, b CHAR(4))") + + var queries = [...]struct { + in string + codes []string + }{ + {"DROP TABLE IF EXISTS no_such_table", []string{"1051"}}, + {"INSERT INTO test VALUES(10,'mysql'),(NULL,'test'),(300,'Open Source')", []string{"1265", "1048", "1264", "1265"}}, + } + var err error + + var checkWarnings = func(err error, mode string, idx int) { + if err == nil { + dbt.Errorf("Expected STRICT error on query [%s] %s", mode, queries[idx].in) + } + + if warnings, ok := err.(MySQLWarnings); ok { + var codes = make([]string, len(warnings)) + for i := range warnings { + codes[i] = warnings[i].Code + } + if len(codes) != len(queries[idx].codes) { + dbt.Errorf("Unexpected STRICT error count on query [%s] %s: Wanted %v, Got %v", mode, queries[idx].in, queries[idx].codes, codes) + } + + for i := range warnings { + if codes[i] != queries[idx].codes[i] { + dbt.Errorf("Unexpected STRICT error codes on query [%s] %s: Wanted %v, Got %v", mode, queries[idx].in, queries[idx].codes, codes) + return + } + } + + } else { + dbt.Errorf("Unexpected error on query [%s] %s: %s", mode, queries[idx].in, err.Error()) + } + } + + // text protocol + for i := range queries { + _, err = dbt.db.Exec(queries[i].in) + checkWarnings(err, "text", i) + } + + var stmt *sql.Stmt + + // binary protocol + for i := range queries { + stmt, err = dbt.db.Prepare(queries[i].in) + if err != nil { + dbt.Errorf("Error on preparing query %s: %s", queries[i].in, err.Error()) + } + + _, err = stmt.Exec() + checkWarnings(err, "binary", i) + + err = stmt.Close() + if err != nil { + dbt.Errorf("Error on closing stmt for query %s: %s", queries[i].in, err.Error()) + } + } + }) +} + +func TestTLS(t *testing.T) { + tlsTest := func(dbt *DBTest) { + if err := dbt.db.Ping(); err != nil { + if err == ErrNoTLS { + dbt.Skip("Server does not support TLS") + } else { + dbt.Fatalf("Error on Ping: %s", err.Error()) + } + } + + rows := dbt.mustQuery("SHOW STATUS LIKE 'Ssl_cipher'") + + var variable, value *sql.RawBytes + for rows.Next() { + if err := rows.Scan(&variable, &value); err != nil { + dbt.Fatal(err.Error()) + } + + if value == nil { + dbt.Fatal("No Cipher") + } + } + } + + runTests(t, dsn+"&tls=skip-verify", tlsTest) + + // Verify that registering / using a custom cfg works + RegisterTLSConfig("custom-skip-verify", &tls.Config{ + InsecureSkipVerify: true, + }) + runTests(t, dsn+"&tls=custom-skip-verify", tlsTest) +} + +func TestReuseClosedConnection(t *testing.T) { + // this test does not use sql.database, it uses the driver directly + if !available { + t.Skipf("MySQL-Server not running on %s", netAddr) + } + + md := &MySQLDriver{} + conn, err := md.Open(dsn) + if err != nil { + t.Fatalf("Error connecting: %s", err.Error()) + } + stmt, err := conn.Prepare("DO 1") + if err != nil { + t.Fatalf("Error preparing statement: %s", err.Error()) + } + _, err = stmt.Exec(nil) + if err != nil { + t.Fatalf("Error executing statement: %s", err.Error()) + } + err = conn.Close() + if err != nil { + t.Fatalf("Error closing connection: %s", err.Error()) + } + + defer func() { + if err := recover(); err != nil { + t.Errorf("Panic after reusing a closed connection: %v", err) + } + }() + _, err = stmt.Exec(nil) + if err != nil && err != driver.ErrBadConn { + t.Errorf("Unexpected error '%s', expected '%s'", + err.Error(), driver.ErrBadConn.Error()) + } +} + +func TestCharset(t *testing.T) { + if !available { + t.Skipf("MySQL-Server not running on %s", netAddr) + } + + mustSetCharset := func(charsetParam, expected string) { + runTests(t, dsn+"&"+charsetParam, func(dbt *DBTest) { + rows := dbt.mustQuery("SELECT @@character_set_connection") + defer rows.Close() + + if !rows.Next() { + dbt.Fatalf("Error getting connection charset: %s", rows.Err()) + } + + var got string + rows.Scan(&got) + + if got != expected { + dbt.Fatalf("Expected connection charset %s but got %s", expected, got) + } + }) + } + + // non utf8 test + mustSetCharset("charset=ascii", "ascii") + + // when the first charset is invalid, use the second + mustSetCharset("charset=none,utf8", "utf8") + + // when the first charset is valid, use it + mustSetCharset("charset=ascii,utf8", "ascii") + mustSetCharset("charset=utf8,ascii", "utf8") +} + +func TestFailingCharset(t *testing.T) { + runTests(t, dsn+"&charset=none", func(dbt *DBTest) { + // run query to really establish connection... + _, err := dbt.db.Exec("SELECT 1") + if err == nil { + dbt.db.Close() + t.Fatalf("Connection must not succeed without a valid charset") + } + }) +} + +func TestCollation(t *testing.T) { + if !available { + t.Skipf("MySQL-Server not running on %s", netAddr) + } + + defaultCollation := "utf8_general_ci" + testCollations := []string{ + "", // do not set + defaultCollation, // driver default + "latin1_general_ci", + "binary", + "utf8_unicode_ci", + "cp1257_bin", + } + + for _, collation := range testCollations { + var expected, tdsn string + if collation != "" { + tdsn = dsn + "&collation=" + collation + expected = collation + } else { + tdsn = dsn + expected = defaultCollation + } + + runTests(t, tdsn, func(dbt *DBTest) { + var got string + if err := dbt.db.QueryRow("SELECT @@collation_connection").Scan(&got); err != nil { + dbt.Fatal(err) + } + + if got != expected { + dbt.Fatalf("Expected connection collation %s but got %s", expected, got) + } + }) + } +} + +func TestRawBytesResultExceedsBuffer(t *testing.T) { + runTests(t, dsn, func(dbt *DBTest) { + // defaultBufSize from buffer.go + expected := strings.Repeat("abc", defaultBufSize) + + rows := dbt.mustQuery("SELECT '" + expected + "'") + defer rows.Close() + if !rows.Next() { + dbt.Error("expected result, got none") + } + var result sql.RawBytes + rows.Scan(&result) + if expected != string(result) { + dbt.Error("result did not match expected value") + } + }) +} + +func TestTimezoneConversion(t *testing.T) { + zones := []string{"UTC", "US/Central", "US/Pacific", "Local"} + + // Regression test for timezone handling + tzTest := func(dbt *DBTest) { + + // Create table + dbt.mustExec("CREATE TABLE test (ts TIMESTAMP)") + + // Insert local time into database (should be converted) + usCentral, _ := time.LoadLocation("US/Central") + reftime := time.Date(2014, 05, 30, 18, 03, 17, 0, time.UTC).In(usCentral) + dbt.mustExec("INSERT INTO test VALUE (?)", reftime) + + // Retrieve time from DB + rows := dbt.mustQuery("SELECT ts FROM test") + if !rows.Next() { + dbt.Fatal("Didn't get any rows out") + } + + var dbTime time.Time + err := rows.Scan(&dbTime) + if err != nil { + dbt.Fatal("Err", err) + } + + // Check that dates match + if reftime.Unix() != dbTime.Unix() { + dbt.Errorf("Times don't match.\n") + dbt.Errorf(" Now(%v)=%v\n", usCentral, reftime) + dbt.Errorf(" Now(UTC)=%v\n", dbTime) + } + } + + for _, tz := range zones { + runTests(t, dsn+"&parseTime=true&loc="+url.QueryEscape(tz), tzTest) + } +} + +// Special cases + +func TestRowsClose(t *testing.T) { + runTests(t, dsn, func(dbt *DBTest) { + rows, err := dbt.db.Query("SELECT 1") + if err != nil { + dbt.Fatal(err) + } + + err = rows.Close() + if err != nil { + dbt.Fatal(err) + } + + if rows.Next() { + dbt.Fatal("Unexpected row after rows.Close()") + } + + err = rows.Err() + if err != nil { + dbt.Fatal(err) + } + }) +} + +// dangling statements +// http://code.google.com/p/go/issues/detail?id=3865 +func TestCloseStmtBeforeRows(t *testing.T) { + runTests(t, dsn, func(dbt *DBTest) { + stmt, err := dbt.db.Prepare("SELECT 1") + if err != nil { + dbt.Fatal(err) + } + + rows, err := stmt.Query() + if err != nil { + stmt.Close() + dbt.Fatal(err) + } + defer rows.Close() + + err = stmt.Close() + if err != nil { + dbt.Fatal(err) + } + + if !rows.Next() { + dbt.Fatal("Getting row failed") + } else { + err = rows.Err() + if err != nil { + dbt.Fatal(err) + } + + var out bool + err = rows.Scan(&out) + if err != nil { + dbt.Fatalf("Error on rows.Scan(): %s", err.Error()) + } + if out != true { + dbt.Errorf("true != %t", out) + } + } + }) +} + +// It is valid to have multiple Rows for the same Stmt +// http://code.google.com/p/go/issues/detail?id=3734 +func TestStmtMultiRows(t *testing.T) { + runTests(t, dsn, func(dbt *DBTest) { + stmt, err := dbt.db.Prepare("SELECT 1 UNION SELECT 0") + if err != nil { + dbt.Fatal(err) + } + + rows1, err := stmt.Query() + if err != nil { + stmt.Close() + dbt.Fatal(err) + } + defer rows1.Close() + + rows2, err := stmt.Query() + if err != nil { + stmt.Close() + dbt.Fatal(err) + } + defer rows2.Close() + + var out bool + + // 1 + if !rows1.Next() { + dbt.Fatal("1st rows1.Next failed") + } else { + err = rows1.Err() + if err != nil { + dbt.Fatal(err) + } + + err = rows1.Scan(&out) + if err != nil { + dbt.Fatalf("Error on rows.Scan(): %s", err.Error()) + } + if out != true { + dbt.Errorf("true != %t", out) + } + } + + if !rows2.Next() { + dbt.Fatal("1st rows2.Next failed") + } else { + err = rows2.Err() + if err != nil { + dbt.Fatal(err) + } + + err = rows2.Scan(&out) + if err != nil { + dbt.Fatalf("Error on rows.Scan(): %s", err.Error()) + } + if out != true { + dbt.Errorf("true != %t", out) + } + } + + // 2 + if !rows1.Next() { + dbt.Fatal("2nd rows1.Next failed") + } else { + err = rows1.Err() + if err != nil { + dbt.Fatal(err) + } + + err = rows1.Scan(&out) + if err != nil { + dbt.Fatalf("Error on rows.Scan(): %s", err.Error()) + } + if out != false { + dbt.Errorf("false != %t", out) + } + + if rows1.Next() { + dbt.Fatal("Unexpected row on rows1") + } + err = rows1.Close() + if err != nil { + dbt.Fatal(err) + } + } + + if !rows2.Next() { + dbt.Fatal("2nd rows2.Next failed") + } else { + err = rows2.Err() + if err != nil { + dbt.Fatal(err) + } + + err = rows2.Scan(&out) + if err != nil { + dbt.Fatalf("Error on rows.Scan(): %s", err.Error()) + } + if out != false { + dbt.Errorf("false != %t", out) + } + + if rows2.Next() { + dbt.Fatal("Unexpected row on rows2") + } + err = rows2.Close() + if err != nil { + dbt.Fatal(err) + } + } + }) +} + +// Regression test for +// * more than 32 NULL parameters (issue 209) +// * more parameters than fit into the buffer (issue 201) +func TestPreparedManyCols(t *testing.T) { + const numParams = defaultBufSize + runTests(t, dsn, func(dbt *DBTest) { + query := "SELECT ?" + strings.Repeat(",?", numParams-1) + stmt, err := dbt.db.Prepare(query) + if err != nil { + dbt.Fatal(err) + } + defer stmt.Close() + // create more parameters than fit into the buffer + // which will take nil-values + params := make([]interface{}, numParams) + rows, err := stmt.Query(params...) + if err != nil { + stmt.Close() + dbt.Fatal(err) + } + defer rows.Close() + }) +} + +func TestConcurrent(t *testing.T) { + if enabled, _ := readBool(os.Getenv("MYSQL_TEST_CONCURRENT")); !enabled { + t.Skip("MYSQL_TEST_CONCURRENT env var not set") + } + + runTests(t, dsn, func(dbt *DBTest) { + var max int + err := dbt.db.QueryRow("SELECT @@max_connections").Scan(&max) + if err != nil { + dbt.Fatalf("%s", err.Error()) + } + dbt.Logf("Testing up to %d concurrent connections \r\n", max) + + var remaining, succeeded int32 = int32(max), 0 + + var wg sync.WaitGroup + wg.Add(max) + + var fatalError string + var once sync.Once + fatalf := func(s string, vals ...interface{}) { + once.Do(func() { + fatalError = fmt.Sprintf(s, vals...) + }) + } + + for i := 0; i < max; i++ { + go func(id int) { + defer wg.Done() + + tx, err := dbt.db.Begin() + atomic.AddInt32(&remaining, -1) + + if err != nil { + if err.Error() != "Error 1040: Too many connections" { + fatalf("Error on Conn %d: %s", id, err.Error()) + } + return + } + + // keep the connection busy until all connections are open + for remaining > 0 { + if _, err = tx.Exec("DO 1"); err != nil { + fatalf("Error on Conn %d: %s", id, err.Error()) + return + } + } + + if err = tx.Commit(); err != nil { + fatalf("Error on Conn %d: %s", id, err.Error()) + return + } + + // everything went fine with this connection + atomic.AddInt32(&succeeded, 1) + }(i) + } + + // wait until all conections are open + wg.Wait() + + if fatalError != "" { + dbt.Fatal(fatalError) + } + + dbt.Logf("Reached %d concurrent connections\r\n", succeeded) + }) +} + +// Tests custom dial functions +func TestCustomDial(t *testing.T) { + if !available { + t.Skipf("MySQL-Server not running on %s", netAddr) + } + + // our custom dial function which justs wraps net.Dial here + RegisterDial("mydial", func(addr string) (net.Conn, error) { + return net.Dial(prot, addr) + }) + + db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@mydial(%s)/%s?timeout=30s&strict=true", user, pass, addr, dbname)) + if err != nil { + t.Fatalf("Error connecting: %s", err.Error()) + } + defer db.Close() + + if _, err = db.Exec("DO 1"); err != nil { + t.Fatalf("Connection failed: %s", err.Error()) + } +} diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/errors.go b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/errors.go new file mode 100644 index 00000000000..97d7b399620 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/errors.go @@ -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 + } + } +} diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/errors_test.go b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/errors_test.go new file mode 100644 index 00000000000..96f9126d679 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/errors_test.go @@ -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") + }) +} diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/infile.go b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/infile.go new file mode 100644 index 00000000000..121a04c712c --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/infile.go @@ -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 ". +// 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::". +// 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 ", 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 +} diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/packets.go b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/packets.go new file mode 100644 index 00000000000..f2e385bf8c9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/packets.go @@ -0,0 +1,1122 @@ +// 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 ( + "bytes" + "crypto/tls" + "database/sql/driver" + "encoding/binary" + "fmt" + "io" + "math" + "time" +) + +// Packets documentation: +// http://dev.mysql.com/doc/internals/en/client-server-protocol.html + +// Read packet to buffer 'data' +func (mc *mysqlConn) readPacket() ([]byte, error) { + var payload []byte + for { + // Read packet header + data, err := mc.buf.readNext(4) + if err != nil { + errLog.Print(err) + mc.Close() + return nil, driver.ErrBadConn + } + + // Packet Length [24 bit] + pktLen := int(uint32(data[0]) | uint32(data[1])<<8 | uint32(data[2])<<16) + + if pktLen < 1 { + errLog.Print(ErrMalformPkt) + mc.Close() + return nil, driver.ErrBadConn + } + + // Check Packet Sync [8 bit] + if data[3] != mc.sequence { + if data[3] > mc.sequence { + return nil, ErrPktSyncMul + } else { + return nil, ErrPktSync + } + } + mc.sequence++ + + // Read packet body [pktLen bytes] + data, err = mc.buf.readNext(pktLen) + if err != nil { + errLog.Print(err) + mc.Close() + return nil, driver.ErrBadConn + } + + isLastPacket := (pktLen < maxPacketSize) + + // Zero allocations for non-splitting packets + if isLastPacket && payload == nil { + return data, nil + } + + payload = append(payload, data...) + + if isLastPacket { + return payload, nil + } + } +} + +// Write packet buffer 'data' +func (mc *mysqlConn) writePacket(data []byte) error { + pktLen := len(data) - 4 + + if pktLen > mc.maxPacketAllowed { + return ErrPktTooLarge + } + + for { + var size int + if pktLen >= maxPacketSize { + data[0] = 0xff + data[1] = 0xff + data[2] = 0xff + size = maxPacketSize + } else { + data[0] = byte(pktLen) + data[1] = byte(pktLen >> 8) + data[2] = byte(pktLen >> 16) + size = pktLen + } + data[3] = mc.sequence + + // Write packet + n, err := mc.netConn.Write(data[:4+size]) + if err == nil && n == 4+size { + mc.sequence++ + if size != maxPacketSize { + return nil + } + pktLen -= size + data = data[size:] + continue + } + + // Handle error + if err == nil { // n != len(data) + errLog.Print(ErrMalformPkt) + } else { + errLog.Print(err) + } + return driver.ErrBadConn + } +} + +/****************************************************************************** +* Initialisation Process * +******************************************************************************/ + +// Handshake Initialization Packet +// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake +func (mc *mysqlConn) readInitPacket() ([]byte, error) { + data, err := mc.readPacket() + if err != nil { + return nil, err + } + + if data[0] == iERR { + return nil, mc.handleErrorPacket(data) + } + + // protocol version [1 byte] + if data[0] < minProtocolVersion { + return nil, fmt.Errorf( + "Unsupported MySQL Protocol Version %d. Protocol Version %d or higher is required", + data[0], + minProtocolVersion, + ) + } + + // server version [null terminated string] + // connection id [4 bytes] + pos := 1 + bytes.IndexByte(data[1:], 0x00) + 1 + 4 + + // first part of the password cipher [8 bytes] + cipher := data[pos : pos+8] + + // (filler) always 0x00 [1 byte] + pos += 8 + 1 + + // capability flags (lower 2 bytes) [2 bytes] + mc.flags = clientFlag(binary.LittleEndian.Uint16(data[pos : pos+2])) + if mc.flags&clientProtocol41 == 0 { + return nil, ErrOldProtocol + } + if mc.flags&clientSSL == 0 && mc.cfg.tls != nil { + return nil, ErrNoTLS + } + pos += 2 + + if len(data) > pos { + // character set [1 byte] + // status flags [2 bytes] + // capability flags (upper 2 bytes) [2 bytes] + // length of auth-plugin-data [1 byte] + // reserved (all [00]) [10 bytes] + pos += 1 + 2 + 2 + 1 + 10 + + // second part of the password cipher [mininum 13 bytes], + // where len=MAX(13, length of auth-plugin-data - 8) + // + // The web documentation is ambiguous about the length. However, + // according to mysql-5.7/sql/auth/sql_authentication.cc line 538, + // the 13th byte is "\0 byte, terminating the second part of + // a scramble". So the second part of the password cipher is + // a NULL terminated string that's at least 13 bytes with the + // last byte being NULL. + // + // The official Python library uses the fixed length 12 + // which seems to work but technically could have a hidden bug. + cipher = append(cipher, data[pos:pos+12]...) + + // TODO: Verify string termination + // EOF if version (>= 5.5.7 and < 5.5.10) or (>= 5.6.0 and < 5.6.2) + // \NUL otherwise + // + //if data[len(data)-1] == 0 { + // return + //} + //return ErrMalformPkt + return cipher, nil + } + + // make a memory safe copy of the cipher slice + var b [8]byte + copy(b[:], cipher) + return b[:], nil +} + +// Client Authentication Packet +// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse +func (mc *mysqlConn) writeAuthPacket(cipher []byte) error { + // Adjust client flags based on server support + clientFlags := clientProtocol41 | + clientSecureConn | + clientLongPassword | + clientTransactions | + clientLocalFiles | + mc.flags&clientLongFlag + + if mc.cfg.clientFoundRows { + clientFlags |= clientFoundRows + } + + // To enable TLS / SSL + if mc.cfg.tls != nil { + clientFlags |= clientSSL + } + + // User Password + scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.passwd)) + + pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.user) + 1 + 1 + len(scrambleBuff) + + // To specify a db name + if n := len(mc.cfg.dbname); n > 0 { + clientFlags |= clientConnectWithDB + pktLen += n + 1 + } + + // Calculate packet length and get buffer with that size + data := mc.buf.takeSmallBuffer(pktLen + 4) + if data == nil { + // can not take the buffer. Something must be wrong with the connection + errLog.Print(ErrBusyBuffer) + return driver.ErrBadConn + } + + // ClientFlags [32 bit] + data[4] = byte(clientFlags) + data[5] = byte(clientFlags >> 8) + data[6] = byte(clientFlags >> 16) + data[7] = byte(clientFlags >> 24) + + // MaxPacketSize [32 bit] (none) + data[8] = 0x00 + data[9] = 0x00 + data[10] = 0x00 + data[11] = 0x00 + + // Charset [1 byte] + data[12] = mc.cfg.collation + + // SSL Connection Request Packet + // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest + if mc.cfg.tls != nil { + // Send TLS / SSL request packet + if err := mc.writePacket(data[:(4+4+1+23)+4]); err != nil { + return err + } + + // Switch to TLS + tlsConn := tls.Client(mc.netConn, mc.cfg.tls) + if err := tlsConn.Handshake(); err != nil { + return err + } + mc.netConn = tlsConn + mc.buf.rd = tlsConn + } + + // Filler [23 bytes] (all 0x00) + pos := 13 + 23 + + // User [null terminated string] + if len(mc.cfg.user) > 0 { + pos += copy(data[pos:], mc.cfg.user) + } + data[pos] = 0x00 + pos++ + + // ScrambleBuffer [length encoded integer] + data[pos] = byte(len(scrambleBuff)) + pos += 1 + copy(data[pos+1:], scrambleBuff) + + // Databasename [null terminated string] + if len(mc.cfg.dbname) > 0 { + pos += copy(data[pos:], mc.cfg.dbname) + data[pos] = 0x00 + } + + // Send Auth packet + return mc.writePacket(data) +} + +// Client old authentication packet +// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse +func (mc *mysqlConn) writeOldAuthPacket(cipher []byte) error { + // User password + scrambleBuff := scrambleOldPassword(cipher, []byte(mc.cfg.passwd)) + + // Calculate the packet lenght and add a tailing 0 + pktLen := len(scrambleBuff) + 1 + data := mc.buf.takeSmallBuffer(4 + pktLen) + if data == nil { + // can not take the buffer. Something must be wrong with the connection + errLog.Print(ErrBusyBuffer) + return driver.ErrBadConn + } + + // Add the scrambled password [null terminated string] + copy(data[4:], scrambleBuff) + data[4+pktLen-1] = 0x00 + + return mc.writePacket(data) +} + +/****************************************************************************** +* Command Packets * +******************************************************************************/ + +func (mc *mysqlConn) writeCommandPacket(command byte) error { + // Reset Packet Sequence + mc.sequence = 0 + + data := mc.buf.takeSmallBuffer(4 + 1) + if data == nil { + // can not take the buffer. Something must be wrong with the connection + errLog.Print(ErrBusyBuffer) + return driver.ErrBadConn + } + + // Add command byte + data[4] = command + + // Send CMD packet + return mc.writePacket(data) +} + +func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error { + // Reset Packet Sequence + mc.sequence = 0 + + pktLen := 1 + len(arg) + data := mc.buf.takeBuffer(pktLen + 4) + if data == nil { + // can not take the buffer. Something must be wrong with the connection + errLog.Print(ErrBusyBuffer) + return driver.ErrBadConn + } + + // Add command byte + data[4] = command + + // Add arg + copy(data[5:], arg) + + // Send CMD packet + return mc.writePacket(data) +} + +func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error { + // Reset Packet Sequence + mc.sequence = 0 + + data := mc.buf.takeSmallBuffer(4 + 1 + 4) + if data == nil { + // can not take the buffer. Something must be wrong with the connection + errLog.Print(ErrBusyBuffer) + return driver.ErrBadConn + } + + // Add command byte + data[4] = command + + // Add arg [32 bit] + data[5] = byte(arg) + data[6] = byte(arg >> 8) + data[7] = byte(arg >> 16) + data[8] = byte(arg >> 24) + + // Send CMD packet + return mc.writePacket(data) +} + +/****************************************************************************** +* Result Packets * +******************************************************************************/ + +// Returns error if Packet is not an 'Result OK'-Packet +func (mc *mysqlConn) readResultOK() error { + data, err := mc.readPacket() + if err == nil { + // packet indicator + switch data[0] { + + case iOK: + return mc.handleOkPacket(data) + + case iEOF: + // someone is using old_passwords + return ErrOldPassword + + default: // Error otherwise + return mc.handleErrorPacket(data) + } + } + return err +} + +// Result Set Header Packet +// http://dev.mysql.com/doc/internals/en/com-query-response.html#packet-ProtocolText::Resultset +func (mc *mysqlConn) readResultSetHeaderPacket() (int, error) { + data, err := mc.readPacket() + if err == nil { + switch data[0] { + + case iOK: + return 0, mc.handleOkPacket(data) + + case iERR: + return 0, mc.handleErrorPacket(data) + + case iLocalInFile: + return 0, mc.handleInFileRequest(string(data[1:])) + } + + // column count + num, _, n := readLengthEncodedInteger(data) + if n-len(data) == 0 { + return int(num), nil + } + + return 0, ErrMalformPkt + } + return 0, err +} + +// Error Packet +// http://dev.mysql.com/doc/internals/en/generic-response-packets.html#packet-ERR_Packet +func (mc *mysqlConn) handleErrorPacket(data []byte) error { + if data[0] != iERR { + return ErrMalformPkt + } + + // 0xff [1 byte] + + // Error Number [16 bit uint] + errno := binary.LittleEndian.Uint16(data[1:3]) + + pos := 3 + + // SQL State [optional: # + 5bytes string] + if data[3] == 0x23 { + //sqlstate := string(data[4 : 4+5]) + pos = 9 + } + + // Error Message [string] + return &MySQLError{ + Number: errno, + Message: string(data[pos:]), + } +} + +// Ok Packet +// http://dev.mysql.com/doc/internals/en/generic-response-packets.html#packet-OK_Packet +func (mc *mysqlConn) handleOkPacket(data []byte) error { + var n, m int + + // 0x00 [1 byte] + + // Affected rows [Length Coded Binary] + mc.affectedRows, _, n = readLengthEncodedInteger(data[1:]) + + // Insert id [Length Coded Binary] + mc.insertId, _, m = readLengthEncodedInteger(data[1+n:]) + + // server_status [2 bytes] + + // warning count [2 bytes] + if !mc.strict { + return nil + } else { + pos := 1 + n + m + 2 + if binary.LittleEndian.Uint16(data[pos:pos+2]) > 0 { + return mc.getWarnings() + } + return nil + } +} + +// Read Packets as Field Packets until EOF-Packet or an Error appears +// http://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnDefinition41 +func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) { + columns := make([]mysqlField, count) + + for i := 0; ; i++ { + data, err := mc.readPacket() + if err != nil { + return nil, err + } + + // EOF Packet + if data[0] == iEOF && (len(data) == 5 || len(data) == 1) { + if i == count { + return columns, nil + } + return nil, fmt.Errorf("ColumnsCount mismatch n:%d len:%d", count, len(columns)) + } + + // Catalog + pos, err := skipLengthEncodedString(data) + if err != nil { + return nil, err + } + + // Database [len coded string] + n, err := skipLengthEncodedString(data[pos:]) + if err != nil { + return nil, err + } + pos += n + + // Table [len coded string] + n, err = skipLengthEncodedString(data[pos:]) + if err != nil { + return nil, err + } + pos += n + + // Original table [len coded string] + n, err = skipLengthEncodedString(data[pos:]) + if err != nil { + return nil, err + } + pos += n + + // Name [len coded string] + name, _, n, err := readLengthEncodedString(data[pos:]) + if err != nil { + return nil, err + } + columns[i].name = string(name) + pos += n + + // Original name [len coded string] + n, err = skipLengthEncodedString(data[pos:]) + if err != nil { + return nil, err + } + + // Filler [uint8] + // Charset [charset, collation uint8] + // Length [uint32] + pos += n + 1 + 2 + 4 + + // Field type [uint8] + columns[i].fieldType = data[pos] + pos++ + + // Flags [uint16] + columns[i].flags = fieldFlag(binary.LittleEndian.Uint16(data[pos : pos+2])) + pos += 2 + + // Decimals [uint8] + columns[i].decimals = data[pos] + //pos++ + + // Default value [len coded binary] + //if pos < len(data) { + // defaultVal, _, err = bytesToLengthCodedBinary(data[pos:]) + //} + } +} + +// Read Packets as Field Packets until EOF-Packet or an Error appears +// http://dev.mysql.com/doc/internals/en/com-query-response.html#packet-ProtocolText::ResultsetRow +func (rows *textRows) readRow(dest []driver.Value) error { + mc := rows.mc + + data, err := mc.readPacket() + if err != nil { + return err + } + + // EOF Packet + if data[0] == iEOF && len(data) == 5 { + return io.EOF + } + + // RowSet Packet + var n int + var isNull bool + pos := 0 + + for i := range dest { + // Read bytes and convert to string + dest[i], isNull, n, err = readLengthEncodedString(data[pos:]) + pos += n + if err == nil { + if !isNull { + if !mc.parseTime { + continue + } else { + switch rows.columns[i].fieldType { + case fieldTypeTimestamp, fieldTypeDateTime, + fieldTypeDate, fieldTypeNewDate: + dest[i], err = parseDateTime( + string(dest[i].([]byte)), + mc.cfg.loc, + ) + if err == nil { + continue + } + default: + continue + } + } + + } else { + dest[i] = nil + continue + } + } + return err // err != nil + } + + return nil +} + +// Reads Packets until EOF-Packet or an Error appears. Returns count of Packets read +func (mc *mysqlConn) readUntilEOF() error { + for { + data, err := mc.readPacket() + + // No Err and no EOF Packet + if err == nil && data[0] != iEOF { + continue + } + return err // Err or EOF + } +} + +/****************************************************************************** +* Prepared Statements * +******************************************************************************/ + +// Prepare Result Packets +// http://dev.mysql.com/doc/internals/en/com-stmt-prepare-response.html +func (stmt *mysqlStmt) readPrepareResultPacket() (uint16, error) { + data, err := stmt.mc.readPacket() + if err == nil { + // packet indicator [1 byte] + if data[0] != iOK { + return 0, stmt.mc.handleErrorPacket(data) + } + + // statement id [4 bytes] + stmt.id = binary.LittleEndian.Uint32(data[1:5]) + + // Column count [16 bit uint] + columnCount := binary.LittleEndian.Uint16(data[5:7]) + + // Param count [16 bit uint] + stmt.paramCount = int(binary.LittleEndian.Uint16(data[7:9])) + + // Reserved [8 bit] + + // Warning count [16 bit uint] + if !stmt.mc.strict { + return columnCount, nil + } else { + // Check for warnings count > 0, only available in MySQL > 4.1 + if len(data) >= 12 && binary.LittleEndian.Uint16(data[10:12]) > 0 { + return columnCount, stmt.mc.getWarnings() + } + return columnCount, nil + } + } + return 0, err +} + +// http://dev.mysql.com/doc/internals/en/com-stmt-send-long-data.html +func (stmt *mysqlStmt) writeCommandLongData(paramID int, arg []byte) error { + maxLen := stmt.mc.maxPacketAllowed - 1 + pktLen := maxLen + + // After the header (bytes 0-3) follows before the data: + // 1 byte command + // 4 bytes stmtID + // 2 bytes paramID + const dataOffset = 1 + 4 + 2 + + // Can not use the write buffer since + // a) the buffer is too small + // b) it is in use + data := make([]byte, 4+1+4+2+len(arg)) + + copy(data[4+dataOffset:], arg) + + for argLen := len(arg); argLen > 0; argLen -= pktLen - dataOffset { + if dataOffset+argLen < maxLen { + pktLen = dataOffset + argLen + } + + stmt.mc.sequence = 0 + // Add command byte [1 byte] + data[4] = comStmtSendLongData + + // Add stmtID [32 bit] + data[5] = byte(stmt.id) + data[6] = byte(stmt.id >> 8) + data[7] = byte(stmt.id >> 16) + data[8] = byte(stmt.id >> 24) + + // Add paramID [16 bit] + data[9] = byte(paramID) + data[10] = byte(paramID >> 8) + + // Send CMD packet + err := stmt.mc.writePacket(data[:4+pktLen]) + if err == nil { + data = data[pktLen-dataOffset:] + continue + } + return err + + } + + // Reset Packet Sequence + stmt.mc.sequence = 0 + return nil +} + +// Execute Prepared Statement +// http://dev.mysql.com/doc/internals/en/com-stmt-execute.html +func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { + if len(args) != stmt.paramCount { + return fmt.Errorf( + "Arguments count mismatch (Got: %d Has: %d)", + len(args), + stmt.paramCount, + ) + } + + const minPktLen = 4 + 1 + 4 + 1 + 4 + mc := stmt.mc + + // Reset packet-sequence + mc.sequence = 0 + + var data []byte + + if len(args) == 0 { + data = mc.buf.takeBuffer(minPktLen) + } else { + data = mc.buf.takeCompleteBuffer() + } + if data == nil { + // can not take the buffer. Something must be wrong with the connection + errLog.Print(ErrBusyBuffer) + return driver.ErrBadConn + } + + // command [1 byte] + data[4] = comStmtExecute + + // statement_id [4 bytes] + data[5] = byte(stmt.id) + data[6] = byte(stmt.id >> 8) + data[7] = byte(stmt.id >> 16) + data[8] = byte(stmt.id >> 24) + + // flags (0: CURSOR_TYPE_NO_CURSOR) [1 byte] + data[9] = 0x00 + + // iteration_count (uint32(1)) [4 bytes] + data[10] = 0x01 + data[11] = 0x00 + data[12] = 0x00 + data[13] = 0x00 + + if len(args) > 0 { + pos := minPktLen + + var nullMask []byte + if maskLen, typesLen := (len(args)+7)/8, 1+2*len(args); pos+maskLen+typesLen >= len(data) { + // buffer has to be extended but we don't know by how much so + // we depend on append after all data with known sizes fit. + // We stop at that because we deal with a lot of columns here + // which makes the required allocation size hard to guess. + tmp := make([]byte, pos+maskLen+typesLen) + copy(tmp[:pos], data[:pos]) + data = tmp + nullMask = data[pos : pos+maskLen] + pos += maskLen + } else { + nullMask = data[pos : pos+maskLen] + for i := 0; i < maskLen; i++ { + nullMask[i] = 0 + } + pos += maskLen + } + + // newParameterBoundFlag 1 [1 byte] + data[pos] = 0x01 + pos++ + + // type of each parameter [len(args)*2 bytes] + paramTypes := data[pos:] + pos += len(args) * 2 + + // value of each parameter [n bytes] + paramValues := data[pos:pos] + valuesCap := cap(paramValues) + + for i, arg := range args { + // build NULL-bitmap + if arg == nil { + nullMask[i/8] |= 1 << (uint(i) & 7) + paramTypes[i+i] = fieldTypeNULL + paramTypes[i+i+1] = 0x00 + continue + } + + // cache types and values + switch v := arg.(type) { + case int64: + paramTypes[i+i] = fieldTypeLongLong + paramTypes[i+i+1] = 0x00 + + if cap(paramValues)-len(paramValues)-8 >= 0 { + paramValues = paramValues[:len(paramValues)+8] + binary.LittleEndian.PutUint64( + paramValues[len(paramValues)-8:], + uint64(v), + ) + } else { + paramValues = append(paramValues, + uint64ToBytes(uint64(v))..., + ) + } + + case float64: + paramTypes[i+i] = fieldTypeDouble + paramTypes[i+i+1] = 0x00 + + if cap(paramValues)-len(paramValues)-8 >= 0 { + paramValues = paramValues[:len(paramValues)+8] + binary.LittleEndian.PutUint64( + paramValues[len(paramValues)-8:], + math.Float64bits(v), + ) + } else { + paramValues = append(paramValues, + uint64ToBytes(math.Float64bits(v))..., + ) + } + + case bool: + paramTypes[i+i] = fieldTypeTiny + paramTypes[i+i+1] = 0x00 + + if v { + paramValues = append(paramValues, 0x01) + } else { + paramValues = append(paramValues, 0x00) + } + + case []byte: + // Common case (non-nil value) first + if v != nil { + paramTypes[i+i] = fieldTypeString + paramTypes[i+i+1] = 0x00 + + if len(v) < mc.maxPacketAllowed-pos-len(paramValues)-(len(args)-(i+1))*64 { + paramValues = appendLengthEncodedInteger(paramValues, + uint64(len(v)), + ) + paramValues = append(paramValues, v...) + } else { + if err := stmt.writeCommandLongData(i, v); err != nil { + return err + } + } + continue + } + + // Handle []byte(nil) as a NULL value + nullMask[i/8] |= 1 << (uint(i) & 7) + paramTypes[i+i] = fieldTypeNULL + paramTypes[i+i+1] = 0x00 + + case string: + paramTypes[i+i] = fieldTypeString + paramTypes[i+i+1] = 0x00 + + if len(v) < mc.maxPacketAllowed-pos-len(paramValues)-(len(args)-(i+1))*64 { + paramValues = appendLengthEncodedInteger(paramValues, + uint64(len(v)), + ) + paramValues = append(paramValues, v...) + } else { + if err := stmt.writeCommandLongData(i, []byte(v)); err != nil { + return err + } + } + + case time.Time: + paramTypes[i+i] = fieldTypeString + paramTypes[i+i+1] = 0x00 + + var val []byte + if v.IsZero() { + val = []byte("0000-00-00") + } else { + val = []byte(v.In(mc.cfg.loc).Format(timeFormat)) + } + + paramValues = appendLengthEncodedInteger(paramValues, + uint64(len(val)), + ) + paramValues = append(paramValues, val...) + + default: + return fmt.Errorf("Can't convert type: %T", arg) + } + } + + // Check if param values exceeded the available buffer + // In that case we must build the data packet with the new values buffer + if valuesCap != cap(paramValues) { + data = append(data[:pos], paramValues...) + mc.buf.buf = data + } + + pos += len(paramValues) + data = data[:pos] + } + + return mc.writePacket(data) +} + +// http://dev.mysql.com/doc/internals/en/binary-protocol-resultset-row.html +func (rows *binaryRows) readRow(dest []driver.Value) error { + data, err := rows.mc.readPacket() + if err != nil { + return err + } + + // packet indicator [1 byte] + if data[0] != iOK { + // EOF Packet + if data[0] == iEOF && len(data) == 5 { + return io.EOF + } + + // Error otherwise + return rows.mc.handleErrorPacket(data) + } + + // NULL-bitmap, [(column-count + 7 + 2) / 8 bytes] + pos := 1 + (len(dest)+7+2)>>3 + nullMask := data[1:pos] + + for i := range dest { + // Field is NULL + // (byte >> bit-pos) % 2 == 1 + if ((nullMask[(i+2)>>3] >> uint((i+2)&7)) & 1) == 1 { + dest[i] = nil + continue + } + + // Convert to byte-coded string + switch rows.columns[i].fieldType { + case fieldTypeNULL: + dest[i] = nil + continue + + // Numeric Types + case fieldTypeTiny: + if rows.columns[i].flags&flagUnsigned != 0 { + dest[i] = int64(data[pos]) + } else { + dest[i] = int64(int8(data[pos])) + } + pos++ + continue + + case fieldTypeShort, fieldTypeYear: + if rows.columns[i].flags&flagUnsigned != 0 { + dest[i] = int64(binary.LittleEndian.Uint16(data[pos : pos+2])) + } else { + dest[i] = int64(int16(binary.LittleEndian.Uint16(data[pos : pos+2]))) + } + pos += 2 + continue + + case fieldTypeInt24, fieldTypeLong: + if rows.columns[i].flags&flagUnsigned != 0 { + dest[i] = int64(binary.LittleEndian.Uint32(data[pos : pos+4])) + } else { + dest[i] = int64(int32(binary.LittleEndian.Uint32(data[pos : pos+4]))) + } + pos += 4 + continue + + case fieldTypeLongLong: + if rows.columns[i].flags&flagUnsigned != 0 { + val := binary.LittleEndian.Uint64(data[pos : pos+8]) + if val > math.MaxInt64 { + dest[i] = uint64ToString(val) + } else { + dest[i] = int64(val) + } + } else { + dest[i] = int64(binary.LittleEndian.Uint64(data[pos : pos+8])) + } + pos += 8 + continue + + case fieldTypeFloat: + dest[i] = float64(math.Float32frombits(binary.LittleEndian.Uint32(data[pos : pos+4]))) + pos += 4 + continue + + case fieldTypeDouble: + dest[i] = math.Float64frombits(binary.LittleEndian.Uint64(data[pos : pos+8])) + pos += 8 + continue + + // Length coded Binary Strings + case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar, + fieldTypeBit, fieldTypeEnum, fieldTypeSet, fieldTypeTinyBLOB, + fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB, + fieldTypeVarString, fieldTypeString, fieldTypeGeometry: + var isNull bool + var n int + dest[i], isNull, n, err = readLengthEncodedString(data[pos:]) + pos += n + if err == nil { + if !isNull { + continue + } else { + dest[i] = nil + continue + } + } + return err + + case + fieldTypeDate, fieldTypeNewDate, // Date YYYY-MM-DD + fieldTypeTime, // Time [-][H]HH:MM:SS[.fractal] + fieldTypeTimestamp, fieldTypeDateTime: // Timestamp YYYY-MM-DD HH:MM:SS[.fractal] + + num, isNull, n := readLengthEncodedInteger(data[pos:]) + pos += n + + switch { + case isNull: + dest[i] = nil + continue + case rows.columns[i].fieldType == fieldTypeTime: + // database/sql does not support an equivalent to TIME, return a string + var dstlen uint8 + switch decimals := rows.columns[i].decimals; decimals { + case 0x00, 0x1f: + dstlen = 8 + case 1, 2, 3, 4, 5, 6: + dstlen = 8 + 1 + decimals + default: + return fmt.Errorf( + "MySQL protocol error, illegal decimals value %d", + rows.columns[i].decimals, + ) + } + dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, true) + case rows.mc.parseTime: + dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.loc) + default: + var dstlen uint8 + if rows.columns[i].fieldType == fieldTypeDate { + dstlen = 10 + } else { + switch decimals := rows.columns[i].decimals; decimals { + case 0x00, 0x1f: + dstlen = 19 + case 1, 2, 3, 4, 5, 6: + dstlen = 19 + 1 + decimals + default: + return fmt.Errorf( + "MySQL protocol error, illegal decimals value %d", + rows.columns[i].decimals, + ) + } + } + dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, false) + } + + if err == nil { + pos += int(num) + continue + } else { + return err + } + + // Please report if this happens! + default: + return fmt.Errorf("Unknown FieldType %d", rows.columns[i].fieldType) + } + } + + return nil +} diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/result.go b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/result.go new file mode 100644 index 00000000000..c6438d0347d --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/result.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/rows.go b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/rows.go new file mode 100644 index 00000000000..5ecaa94931b --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/rows.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/statement.go b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/statement.go new file mode 100644 index 00000000000..142ef5416ae --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/statement.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/transaction.go b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/transaction.go new file mode 100644 index 00000000000..33c749b35c4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/transaction.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/utils.go b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/utils.go new file mode 100644 index 00000000000..56f1b082ef0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/utils.go @@ -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¶mN=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&...¶mN=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)) +} diff --git a/Godeps/_workspace/src/github.com/go-sql-driver/mysql/utils_test.go b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/utils_test.go new file mode 100644 index 00000000000..6e50b09b9cb --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-sql-driver/mysql/utils_test.go @@ -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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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) +} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions.go deleted file mode 100644 index ee5433e3b88..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions.go +++ /dev/null @@ -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 -) diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/collections.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/collections.go deleted file mode 100644 index 240aac4a5bf..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/collections.go +++ /dev/null @@ -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) -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/collections_test.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/collections_test.go deleted file mode 100644 index 25612bd7670..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/collections_test.go +++ /dev/null @@ -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 )!") - 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 )!") - - 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 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)) -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/doc.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/doc.go deleted file mode 100644 index fe52b0853b5..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/doc.go +++ /dev/null @@ -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 diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/equality.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/equality.go deleted file mode 100644 index 082c680ef04..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/equality.go +++ /dev/null @@ -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 -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/equality_test.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/equality_test.go deleted file mode 100644 index 67ff57a692b..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/equality_test.go +++ /dev/null @@ -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 '' to NOT be nil (but it was)!") - pass(t, so(1, ShouldNotBeNil)) - - var thing Thinger - fail(t, so(thing, ShouldNotBeNil), "Expected '' 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)) -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/filter.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/filter.go deleted file mode 100644 index 872e58c5407..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/filter.go +++ /dev/null @@ -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 -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/init.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/init.go deleted file mode 100644 index 5efdf3f0f4d..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/init.go +++ /dev/null @@ -1,7 +0,0 @@ -package assertions - -var serializer Serializer - -func init() { - serializer = newSerializer() -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/messages.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/messages.go deleted file mode 100644 index ce481a1a42a..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/messages.go +++ /dev/null @@ -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!)" -) diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/panic.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/panic.go deleted file mode 100644 index 7e75db1784b..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/panic.go +++ /dev/null @@ -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 -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/panic_test.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/panic_test.go deleted file mode 100644 index 15eafac4fbb..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/panic_test.go +++ /dev/null @@ -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")) -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/quantity.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/quantity.go deleted file mode 100644 index 19e697bf3ce..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/quantity.go +++ /dev/null @@ -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 -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/quantity_test.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/quantity_test.go deleted file mode 100644 index 7546e7250a8..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/quantity_test.go +++ /dev/null @@ -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)!") -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/serializer.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/serializer.go deleted file mode 100644 index 81b66ce11cf..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/serializer.go +++ /dev/null @@ -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{} -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/serializer_test.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/serializer_test.go deleted file mode 100644 index ba6a36e1f42..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/serializer_test.go +++ /dev/null @@ -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) - } -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/strings.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/strings.go deleted file mode 100644 index 1b887b1191e..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/strings.go +++ /dev/null @@ -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 = "" - } - if prefix == "" { - prefix = "" - } - 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 = "" - } - if suffix == "" { - suffix = "" - } - 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 -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/strings_test.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/strings_test.go deleted file mode 100644 index eec9440ed0a..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/strings_test.go +++ /dev/null @@ -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 '' NOT to start with '' (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 '' NOT to end with '' (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)) -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/time.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/time.go deleted file mode 100644 index 7e05026143f..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/time.go +++ /dev/null @@ -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 "" -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/time_test.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/time_test.go deleted file mode 100644 index f9dda8f8f34..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/time_test.go +++ /dev/null @@ -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) -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/type.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/type.go deleted file mode 100644 index 3fc00f68cd0..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/type.go +++ /dev/null @@ -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 -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/type_test.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/type_test.go deleted file mode 100644 index 4b8d1984670..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/type_test.go +++ /dev/null @@ -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||Expected '' to be: 'int' (but was: '')!") - 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 '' to NOT be: '' (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)) -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/utilities_for_test.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/utilities_for_test.go deleted file mode 100644 index f3ae7210f76..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/assertions/utilities_for_test.go +++ /dev/null @@ -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) -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/context.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/context.go deleted file mode 100644 index f55c1473571..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/context.go +++ /dev/null @@ -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) diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/discovery.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/discovery.go deleted file mode 100644 index b7310f2b058..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/discovery.go +++ /dev/null @@ -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())." diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/doc.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/doc.go deleted file mode 100644 index ec5322a9acc..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/doc.go +++ /dev/null @@ -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...) -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/focused_execution_test.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/focused_execution_test.go deleted file mode 100644 index 294e32fa17e..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/focused_execution_test.go +++ /dev/null @@ -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) -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/gotest/utils.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/gotest/utils.go deleted file mode 100644 index b370f1e4cd0..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/gotest/utils.go +++ /dev/null @@ -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 "" // 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 = "", -1, "" - return // panic? -} - -const maxStackDepth = 100 // This had better be enough... - -var callStack []uintptr = make([]uintptr, maxStackDepth, maxStackDepth) diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/init.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/init.go deleted file mode 100644 index 9d9783e9b7d..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/init.go +++ /dev/null @@ -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 -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/isolated_execution_test.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/isolated_execution_test.go deleted file mode 100644 index 0b79b195d53..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/isolated_execution_test.go +++ /dev/null @@ -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 "" -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/registration.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/registration.go deleted file mode 100644 index a658195a033..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/registration.go +++ /dev/null @@ -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 ®istration{ - 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() -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/console.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/console.go deleted file mode 100644 index 7bf67dbb2b1..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/console.go +++ /dev/null @@ -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) -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/doc.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/doc.go deleted file mode 100644 index a37d0019466..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/doc.go +++ /dev/null @@ -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 diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/dot.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/dot.go deleted file mode 100644 index 47d57c6b0d9..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/dot.go +++ /dev/null @@ -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 -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/dot_test.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/dot_test.go deleted file mode 100644 index a8d20d46f08..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/dot_test.go +++ /dev/null @@ -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) - } -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/gotest.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/gotest.go deleted file mode 100644 index c396e16b17a..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/gotest.go +++ /dev/null @@ -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 == "" -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/gotest_test.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/gotest_test.go deleted file mode 100644 index e15d619ae27..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/gotest_test.go +++ /dev/null @@ -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 -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/init.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/init.go deleted file mode 100644 index ea7a4be075f..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/init.go +++ /dev/null @@ -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() -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/json.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/json.go deleted file mode 100644 index 38c350450e5..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/json.go +++ /dev/null @@ -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: - -` diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/printer.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/printer.go deleted file mode 100644 index 6d4a879c40d..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/printer.go +++ /dev/null @@ -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) diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/printer_test.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/printer_test.go deleted file mode 100644 index 94202d5ac97..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/printer_test.go +++ /dev/null @@ -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) -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/problems.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/problems.go deleted file mode 100644 index c610ba88693..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/problems.go +++ /dev/null @@ -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{} -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/problems_test.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/problems_test.go deleted file mode 100644 index 34b8c2c4049..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/problems_test.go +++ /dev/null @@ -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 -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/reporter.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/reporter.go deleted file mode 100644 index cce6c5e4388..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/reporter.go +++ /dev/null @@ -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 -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/reporter_test.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/reporter_test.go deleted file mode 100644 index 2d5f1ab8c55..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/reporter_test.go +++ /dev/null @@ -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 (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 -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/reports.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/reports.go deleted file mode 100644 index 63e1829d139..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/reports.go +++ /dev/null @@ -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", -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/statistics.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/statistics.go deleted file mode 100644 index e015031c3a3..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/statistics.go +++ /dev/null @@ -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" -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/story.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/story.go deleted file mode 100644 index 42584c0e19f..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting/story.go +++ /dev/null @@ -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 -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting_hooks_test.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting_hooks_test.go deleted file mode 100644 index 33ab3e46e28..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/reporting_hooks_test.go +++ /dev/null @@ -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) diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/runner.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/runner.go deleted file mode 100644 index 592daddb217..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/runner.go +++ /dev/null @@ -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{} } diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/scope.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/scope.go deleted file mode 100644 index a955b534245..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/scope.go +++ /dev/null @@ -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), - } -} diff --git a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/story_conventions_test.go b/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/story_conventions_test.go deleted file mode 100644 index 80cb6cefa6f..00000000000 --- a/Godeps/_workspace/src/github.com/smartystreets/goconvey/convey/story_conventions_test.go +++ /dev/null @@ -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) -} diff --git a/Godeps/_workspace/src/gopkg.in/ini.v1/README.md b/Godeps/_workspace/src/gopkg.in/ini.v1/README.md index 6c5e62ec138..626a6ae5c53 100644 --- a/Godeps/_workspace/src/gopkg.in/ini.v1/README.md +++ b/Godeps/_workspace/src/gopkg.in/ini.v1/README.md @@ -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 `%()s`, where `` is the key name in same section or default section, and `%()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) diff --git a/Godeps/_workspace/src/gopkg.in/ini.v1/README_ZH.md b/Godeps/_workspace/src/gopkg.in/ini.v1/README_ZH.md index a973e904555..2dc38df4a42 100644 --- a/Godeps/_workspace/src/gopkg.in/ini.v1/README_ZH.md +++ b/Godeps/_workspace/src/gopkg.in/ini.v1/README_ZH.md @@ -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) diff --git a/Godeps/_workspace/src/gopkg.in/ini.v1/ini.go b/Godeps/_workspace/src/gopkg.in/ini.v1/ini.go index 40c5bd7c0ee..be45f25e4ac 100644 --- a/Godeps/_workspace/src/gopkg.in/ini.v1/ini.go +++ b/Godeps/_workspace/src/gopkg.in/ini.v1/ini.go @@ -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 { diff --git a/Godeps/_workspace/src/gopkg.in/ini.v1/ini_test.go b/Godeps/_workspace/src/gopkg.in/ini.v1/ini_test.go index 4bec9bb328a..aafe91826e5 100644 --- a/Godeps/_workspace/src/gopkg.in/ini.v1/ini_test.go +++ b/Godeps/_workspace/src/gopkg.in/ini.v1/ini_test.go @@ -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) }) } diff --git a/Godeps/_workspace/src/gopkg.in/ini.v1/struct.go b/Godeps/_workspace/src/gopkg.in/ini.v1/struct.go new file mode 100644 index 00000000000..1875faf7695 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ini.v1/struct.go @@ -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...) +} diff --git a/Godeps/_workspace/src/gopkg.in/ini.v1/struct_test.go b/Godeps/_workspace/src/gopkg.in/ini.v1/struct_test.go new file mode 100644 index 00000000000..97cf7452705 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ini.v1/struct_test.go @@ -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") + }) +}