Skip to content

Commit

Permalink
V2 (#7)
Browse files Browse the repository at this point in the history
* add v2

* add docs

* add script to create database

* restructure

* add postgres

* update readme

* update readme

* update package doc

* remove SetGlobalEngine

* update api

* update readme
  • Loading branch information
khaiql authored Oct 20, 2017
1 parent 5ba4509 commit ea8f278
Show file tree
Hide file tree
Showing 23 changed files with 338 additions and 720 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,9 @@ go:
- tip
before_install:
- go get github.com/mattn/goveralls
- mysql -e 'CREATE DATABASE IF NOT EXISTS dbcleaner_test'
- mysql -u root --default-character-set=utf8 dbcleaner_test < fixtures/mysql_schema.sql
- psql -U postgres -c "CREATE DATABASE dbcleaner_test"
- psql -U postgres -d dbcleaner_test -f fixtures/postgres_schema.sql
script:
- $HOME/gopath/bin/goveralls -service=travis-ci
83 changes: 26 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,79 +6,58 @@ Clean database for testing, inspired by [database_cleaner](https://github.com/Da

## Basic usage

* Getting started: `go get -u github.com/khaiql/dbcleaner`
* To get the package, execute:

```bash
go get gopkg.in/khaiql/dbcleaner.v2
```
import (
"os"
"testing"
"github.com/khaiql/dbcleaner"
// Register postgres db driver, ignore this if you have already called it
somewhere else
_ "github.com/lib/pq"
// Register postgres cleaner helper
_ "github.com/khaiql/dbcleaner/helper/postgres"

)
func TestMain(m *testing.Main) {
cleaner, err := dbcleaner.New("postgres", "YOUR_DB_CONNECTION_STRING")
if err != nil {
panic(err)
}
defer cleaner.Close()
* To import this package, add the following line to your code:

code := m.Run()
cleaner.TruncateTablesExclude("migrations")
```go
import "gopkg.in/khaiql/dbcleaner.v2"
```

os.Exit(code)
}
* To install `TestSuite`:

func TestSomething(t *testing.T) {
// TODO: Write your db related test
}
```bash
go get github.com/stretchr/testify
```

**NOTE:** using `TestMain` will only clear database once after all test cases
* For people who is using old version (v1.0), please change your import to
```go
import "gopkg.in/khaiql/dbcleaner.v1"
```

## Using with testify's suite (recommended)
## Using with testify's suite

```
import (
"testing"
"github.com/khaiql/dbcleaner"
_ "github.com/khaiql/dbcleaner/helper/postgres"
"gopkg.in/khaiql/dbcleaner.v2"
"gopkg.in/khaiql/dbcleaner.v2/engine"
"github.com/stretchr/testify/suite"
)
var Cleaner = dbcleaner.New()
type ExampleSuite struct {
suite.Suite
DBCleaner *dbcleaner.DBCleaner
}
// Init dbcleaner instance at the beginning of every suite
func (suite *ExampleSuite) SetupSuite() {
cleaner, err := dbcleaner.New("postgres", "YOUR_DB_CONNECTION_STRING")
if err != nil {
panic(err)
}
suite.DBCleaner = cleaner
// Init and set mysql cleanup engine
mysql := engine.NewMySQLEngine("YOUR_DB_DSN")
Cleaner.SetEngine(mysql)
}
// Close and release connection at the end of suite
func (suite *ExampleSuite) TearDownSuite() {
suite.DBCleaner.Close()
func (suite *ExampleSuite) SetupTest() {
Cleaner.Acquire("users")
}
// Truncate tables after every test case. Note: sub-test using t.Run wouldn't be
// taken into account
func (suite *ExampleSuite) TearDownTest() {
suite.DBCleaner.TruncateTablesExclude("migrations")
Cleaner.Clean("users")
}
func (suite *ExampleSuite) TestSomething() {
Expand All @@ -102,17 +81,7 @@ Basically all drivers supported by `database/sql` package are also supported by
`dbcleaner`. Check list of drivers:
[https://github.com/golang/go/wiki/SQLDrivers](https://github.com/golang/go/wiki/SQLDrivers)

The mechanism is literally the same as `sql.RegisterDriver`. All you need is to
implement `helper.Helper` interface and call `dbcleaner.RegisterHelper`

Want example? Check [this](https://github.com/khaiql/dbcleaner/tree/master/helper/pq)

Please feel free to create PR for integrating more db drivers

## Running test

1. `docker-compose up -d`
1. `go get -u github.com/lib/pq github.com/go-sql-driver/mysql`
For custom driver, implement your own `engine.Engine` interface and call `SetEngine` on `dbcleaner.Cleaner` instance.

## License

Expand Down
135 changes: 45 additions & 90 deletions dbcleaner.go
Original file line number Diff line number Diff line change
@@ -1,122 +1,77 @@
// Package dbcleaner helps cleaning up database's tables upon unit test.
// With the help of https://github.com/stretchr/testify/tree/master/suite, we can easily
// acquire the tables using in the test in SetupTest or SetupSuite, and cleanup all data
// using TearDownTest or TearDownSuite
package dbcleaner

import (
"database/sql"
"errors"
"log"
"sync"

"github.com/khaiql/dbcleaner/helper"
"github.com/khaiql/dbcleaner/utils"
"github.com/khaiql/dbcleaner/engine"
)

// DBCleaner instance of cleaner that can perform cleaning tables data
type DBCleaner struct {
db *sql.DB
driver string
}
// DbCleaner interface
type DbCleaner interface {
// SetEngine sets dbEngine, can be mysql, postgres...
SetEngine(dbEngine engine.Engine)

var (
mutex sync.Mutex
registeredHelpers = make(map[string]helper.Helper)
// Acquire will lock tables passed in params so data in the table would not be deleted by other test cases
Acquire(tables ...string)

// ErrHelperNotFound return when calling an unregistered Helper
ErrHelperNotFound = errors.New("Helper has not been registered")
)
// Clean calls Truncate the tables
Clean(tables ...string) error

// RegisterHelper register an Helper instance for a particular driver
func RegisterHelper(driverName string, helper helper.Helper) {
mutex.Lock()
defer mutex.Unlock()
registeredHelpers[driverName] = helper
// Close calls corresponding method on dbEngine to release connection to db
Close() error
}

// New returns a Cleaner instance for a particular driver using provided
// connectionString
func New(driver, connectionString string) (*DBCleaner, error) {
db, err := sql.Open(driver, connectionString)
// ErrTableNeverLockBefore is paniced if calling Release on table that havent' been acquired before
var ErrTableNeverLockBefore = errors.New("Table has never been locked before")

if err != nil {
return nil, err
// New returns a default Cleaner with Noop Engine. Call SetEngine to set an actual working engine
func New() DbCleaner {
return &cleanerImpl{
locks: map[string]*sync.RWMutex{},
dbEngine: &engine.NoOp{},
}

return &DBCleaner{db, driver}, err
}

// FindHelper return a registered Helper using driver name
func FindHelper(driver string) (helper.Helper, error) {
if helper, ok := registeredHelpers[driver]; ok {
return helper, nil
}

return nil, ErrHelperNotFound
type cleanerImpl struct {
locks map[string]*sync.RWMutex
dbEngine engine.Engine
}

// Close closes connection to database
func (c *DBCleaner) Close() error {
return c.db.Close()
func (c *cleanerImpl) SetEngine(dbEngine engine.Engine) {
c.dbEngine = dbEngine
}

// TruncateTables truncates data of all tables
func (c *DBCleaner) TruncateTables() error {
return c.TruncateTablesExclude()
}
func (c *cleanerImpl) Acquire(tables ...string) {
for _, table := range tables {
if c.locks[table] == nil {
c.locks[table] = new(sync.RWMutex)
}

// TruncateTablesExclude truncates data of all tables but exclude some specify
// in the list
func (c *DBCleaner) TruncateTablesExclude(excludedTables ...string) error {
tables, err := c.getTables()
if err != nil {
return err
c.locks[table].RLock()
}

tables = utils.SubtractStringArray(tables, excludedTables)
return c.TruncateSelectedTables(tables...)
}

// TruncateSelectedTables truncates data of included tables
func (c *DBCleaner) TruncateSelectedTables(tables ...string) error {
helper, err := FindHelper(c.driver)
if err != nil {
return err
}

var wg sync.WaitGroup
wg.Add(len(tables))

func (c *cleanerImpl) Clean(tables ...string) error {
for _, table := range tables {
go func(tbl string) {
cmd := helper.TruncateTableCommand(tbl)
if _, err := c.db.Exec(cmd); err != nil {
log.Fatalf("Failed to truncate table %s. Error: %s", tbl, err.Error())
}
wg.Done()
}(table)
}
if c.locks[table] != nil {
c.locks[table].RUnlock()
c.locks[table].Lock()
defer c.locks[table].Unlock()
}

wg.Wait()
if err := c.dbEngine.Truncate(table); err != nil {
return err
}
}

return nil
}

func (c *DBCleaner) getTables() ([]string, error) {
tables := make([]string, 0)
helper, err := FindHelper(c.driver)
if err != nil {
return tables, err
}

rows, err := c.db.Query(helper.GetTablesQuery())
if err != nil {
return tables, err
}
defer rows.Close()
for rows.Next() {
var value string
if err = rows.Scan(&value); err == nil {
tables = append(tables, value)
}
}

return tables, nil
func (c *cleanerImpl) Close() error {
return c.dbEngine.Close()
}
Loading

0 comments on commit ea8f278

Please sign in to comment.