Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

RSpec2 upgrade, faster :truncation strategy, spec/support/ design to test real db interactions (for AR), already working travis setup. (mainly addresses #126) #127

Closed
wants to merge 6 commits into from

3 participants

Stanislaw Pankevich Ben Mabey Vito Botta
Stanislaw Pankevich

1) 'Shared example group 'a generic strategy' already exists'.

See https://www.relishapp.com/rspec/rspec-core/docs/example-groups/shared-examples

If shared examples are in a separate file, it should be named *.rb, not
_spec.rb. This was the problem of double inclusion.

2) I did an upgrade to RSpec2.

3 of 4 specs are failing because of I don't have MongoDB, and right now
it is not an easy task to install it because of messy dependendencies my
Gentoo version has.

About the fourth failing spec - configuration_spec#210 - it seems to me that its flow is broken. Please, check carefully what you test there.

3) lib/database_cleaner/active_record/truncation.rb.

See the pieces of code for MySQL and MySQL2 and PostgreSQL adapters: they look very
similar at first glance, but notice the difference how 'mysql' and
'mysql2' gem handle parsing of results - each gem does it its own way. PostgreSQL fast truncation methods in their turn operate on groups of tables, while MySQL and MySQL2 operate each on one table. By exposing this, I just mean, that it is hard to write some common code they all reuse. I think it is up to you to decide, whether fast truncation methods deserve their own module in fx:
lib/database_cleaner/active_record/fast_truncation.rb, like

module DatabaseCleaner
  module ActiveRecord
    module FastTruncation
      module MySQL
      module MySQL2
      module PostgreSQL
      # ...

4) I made additional option :fast. Let's take some time for testing fast
truncation among the users which will like to try it. Also, it is good to have
it as an option, to just compare performance thus timing advantage, when
running large test suites! After you gather feedback from developers,
who succeeded using it, you can easily remove :fast option, making the
fast strategy used by default.

5) Travis

You need:

Go on http://travis-ci.org/ - an sign in there with your github account,
then visit your 'Profile' page, then toggle into on your database_cleaner
repository.

Then go to the 'home' page, 'My repositories' tab there and see if
database_cleaner appeared to begin doing builds.

Also here: https://github.com/bmabey/database_cleaner/hooks you will
see that travis hook activated - click on it - then to press "Test hook"
to begin doing first build.

Remember, that I've already added status image that points to database_cleaner
on travis-ci.org. It is not displayed before you enable database_cleaner on
travis.

.travis.yml - contains "1.9.3" version commented now, I'm sure specs should all pass too, it is 'linecache' gem which refuses to compile on Travis workers having Ubuntu. We need to check it, and all will be fine with 1.9.3.

Ben Mabey
Owner

Thanks @stanislaw! I've merged in the RSpec2 upgrade already and have been going over the fast truncation stuff now.

In DatabaseCleaner's README I mention that the :deletion strategy has been reported to be faster than truncate in certain circumstances. I decided to update your benchmark to use DELETE to see how it compares. These are the results I get with no records:

Truncate non-empty tables (AUTO_INCREMENT ensured)
  0.010000   0.010000   0.020000 (  0.028967)
Truncate non-empty tables (AUTO_INCREMENT is not ensured)
  0.010000   0.000000   0.010000 (  0.010334)
Truncate all tables one by one:
  0.000000   0.000000   0.000000 (  0.035987)
Truncate all tables with DatabaseCleaner:
  0.010000   0.000000   0.010000 (  0.036416)
Delete all tables one by one:
  0.000000   0.000000   0.000000 (  0.007189)
Delete non-empty tables one by one:
  0.000000   0.000000   0.000000 (  0.009258)

These are the results I get with 100 records:

Truncate non-empty tables (AUTO_INCREMENT ensured) 
  0.010000   0.000000   0.010000 (  0.042821)
Truncate non-empty tables (AUTO_INCREMENT is not ensured)
  0.010000   0.010000   0.020000 (  0.043030)
Truncate all tables one by one:
  0.000000   0.000000   0.000000 (  0.034902)
Truncate all tables with DatabaseCleaner:
  0.010000   0.000000   0.010000 (  0.085237)
Delete all tables one by one:
  0.000000   0.000000   0.000000 (  0.058040)
Delete non-empty tables one by one:
  0.020000   0.010000   0.030000 (  0.078900)

Here is running the same benchmark above but doing it 10 times for each method:

Truncate non-empty tables (AUTO_INCREMENT ensured)
  0.260000   0.040000   0.300000 (  1.164267)
Truncate non-empty tables (AUTO_INCREMENT is not ensured)
  0.300000   0.040000   0.340000 (  2.160942)
Truncate all tables one by one:
  0.180000   0.030000   0.210000 (  1.510205)
Truncate all tables with DatabaseCleaner:
  0.190000   0.020000   0.210000 (  1.137671)
Delete all tables one by one:
  0.200000   0.030000   0.230000 (  1.612719)
Delete non-empty tables one by one:
  0.250000   0.040000   0.290000 (  1.585001)

(I should really be taking the average and standard deviation of each run and plotting them, but I don't have the time to do that now. I am skeptical of this benchmark in general since I do see a good amount of variance. If I get more time I'll improve on it.)

The benchmark isn't perfect but the general trends I've seen is that:

  • When you have a lot of tables that haven't been modified under a test then the fastest approach is to use DELETE (or the :deletion strategy in DatabaseCleaner).
  • When all your tables are populated truncating all the tables (without any checks) is the fastest approach.

In real life situations you will end up with a mix of the above. That is why for some people DELETE is faster for them, while for others TRUNCATE is faster. It all depends on the test suite. That said, I would bet that on average DELETE would be the better option and I should probably update the README to reflect this finding.

Have you tried using the :deletion strategy on your test suite? I suspect that it would outperform your fast truncate. Give it a try and let me know how it goes.

Stanislaw Pankevich

I push improved procedure in https://github.com/stanislaw/truncate-vs-count, please run it to see results yourself. Now I'm going to write expanded answer about your comment.

Stanislaw Pankevich

MySQL: DELETE wins only in the case if it operates on empty tables. This is very specific situation, so when building a strategy covering all possible test cases you cannot rely on just DELETE without SELECT EXISTS check, if you want a win for all setups of N & NUM_RECORDS. SELECT EXISTS CHECKS - are very fast (less than 1ms), so you can improve :delete strategy for both MySQL and PostgreSQL with fast option exactly using the code you've added to mysql code in truncate_vs_count repository (I've added lightly modified version using DELETE FROM for PG as well).

PostgreSQL: DELETE operates AMAZINGLY fast, faster than TRUNCATE. It even outperforms fast truncation - it is very big difference. But! it is only in PG. In MySQL DELETE DOESN'T behave SUCH FAST!

But the most important point, that your comment actually is out of scope of my pull request addresses. I was acting in the scope of :truncation strategy DatabaseCleaner has, so I think my PR code could be merged in :truncation strategy without any change.

But if you want the best performance possible, we could think of kind of :mixed strategy for DatabaseCleaner, having TRUNCATE and DELETE in appropriate places. But this not an easy task to guess the best option working for all versions of PG and MySQL and do a test covering all possible cases.

Stanislaw Pankevich

General conclusion I make is:
Truncation strategy works its best when :fast is enabled (code from my PR) for both MySQL and PG.
Deletion strategy works the best as it is for PostgreSQL and with DELETE FROM on empty tables for MySQL.
I thing this not about creating :mixed strategy, but just using :fast truncation for MySQL and :deletion strategy for PG (actually non-empty deletion does not improve deletion strategy for PG somehow seriously, so we could just drop the idea of possible adding :fast option to deletion strategy for any adapters at all).

What do you think?

Stanislaw Pankevich

I've run more different setups of N, NUM_RECORDS - so generalizing it even more:

Best strategy for MySQL is fast :truncation,
for PG - :deletion with checking empty tables.

I am waiting for your results.

Stanislaw Pankevich

And the last: for MySQL fast :deletion is faster in general than just :deletion.

Stanislaw Pankevich

I did real testing of :fast truncation strategy applied to my rather large project using MySQL and having large number of tables.

I did the following setup of my env.rb file:

# Null strategy to force Cucumber to not interact with DatabaseCleaner
# in any possible ways.
# For example, Cucumber's default :truncation js strategy 
# is hardcoded to use DatabaseCleaner in old non-fast way.
class NullStrategy < Cucumber::Rails::Database::Strategy
  def before_js
  end

  def before_non_js
  end
end

Cucumber::Rails::Database.javascript_strategy = NullStrategy

AfterConfiguration do
   DatabaseCleaner.clean_with :truncation, { :fast => true, :reset_ids => false }
end

After do
  DatabaseCleaner.clean_with :truncation, { :fast => true, :reset_ids => false }
end

It works AMAZINGLY FAST without any problems. I am going to use my fork until the moment when you will merge :fast truncation so that I hope very much.

Also I can suggest myself doing analogous commit for fast :deletion with counts strategy, if you like.

Ben Mabey
Owner

Would you mind sharing more information about your particular app? For example, how many tables exist, how many tables on average does a single scenario populate.. Also, it would be interesting to see the time difference between :truncation, :truncation, { :fast => true, :reset_ids => false } and :deletion so I have an idea of the kind of gains other people may expect to see.

Ben Mabey
Owner

BTW, here are the results from my computer (MySQL)...

With no records:

Truncate non-empty tables (AUTO_INCREMENT ensured)
  0.010000   0.000000   0.010000 (  0.030524)
Truncate non-empty tables (AUTO_INCREMENT is not ensured)
  0.000000   0.000000   0.000000 (  0.009543)
Truncate all tables one by one:
  0.000000   0.000000   0.000000 (  0.031045)
Truncate all tables with DatabaseCleaner:
  0.010000   0.000000   0.010000 (  0.031126)
Delete all tables one by one:
  0.010000   0.000000   0.010000 (  0.007151)
Delete non-empty tables one by one:
  0.000000   0.000000   0.000000 (  0.008464)

With 100 records in each of the 30 tables:

Truncate non-empty tables (AUTO_INCREMENT ensured)
  0.010000   0.010000   0.020000 (  0.062481)
Truncate non-empty tables (AUTO_INCREMENT is not ensured)
  0.010000   0.010000   0.020000 (  0.041582)
Truncate all tables one by one:
  0.010000   0.000000   0.010000 (  0.032512)
Truncate all tables with DatabaseCleaner:
  0.010000   0.000000   0.010000 (  0.051319)
Delete all tables one by one:
  0.010000   0.010000   0.020000 (  0.058828)
Delete non-empty tables one by one:
  0.010000   0.000000   0.010000 (  0.099430)

And because I was curious, here is when only 5 of the 30 tables are populated with 100 records (since often time in tests you may not touch the entire system):

Truncate non-empty tables (AUTO_INCREMENT ensured)
  0.010000   0.000000   0.010000 (  0.048525)
Truncate non-empty tables (AUTO_INCREMENT is not ensured)
  0.010000   0.000000   0.010000 (  0.014333)
Truncate all tables one by one:
  0.000000   0.000000   0.000000 (  0.030471)
Truncate all tables with DatabaseCleaner:
  0.000000   0.000000   0.000000 (  0.031658)
Delete all tables one by one:
  0.000000   0.000000   0.000000 (  0.014889)
Delete non-empty tables one by one:
  0.000000   0.000000   0.000000 (  0.018202)
Stanislaw Pankevich

I wonder, how DELETE can perform so fast on your machine. I've NEVER seen, it behaves such fast on two mine machines. I use MySQL 5.1.62-r1 on Gentoo Linux.

Ben Mabey
Owner

I'm using 5.1.49 on OSx Lion, on a Macbook Pro with a 2.53 GHz Intel Core i5.

Stanislaw Pankevich

Would you mind sharing more information about your particular app?

Sure!

I have Rails 3.2.6 app, 46 tables total. MySQL and 'mysql2' gem.

My test suite has 74 scenarios, 588 steps.

Each test fills 3-10 tables.

DatabaseCleaner.strategy = :truncation
# 9m56.995s
DatabaseCleaner.strategy = :deletion
# 5m32,286s
DatabaseCleaner.strategy = :truncation, { :fast => true, :reset_ids => false }
# 5m0.997s

UPD: As I said earlier, I can't rely on "shared connection + :transaction strategy" now because of :webkit Capybara driver, so these are the only options I can use. Definitely, I will be using :truncation, { :fast => true, :reset_ids => false }, in all my Rails projects having webkit or poltergeist.

Stanislaw Pankevich

Are you going to merge this commits? I am interested in this, because I am still holding my attention on this case, thus being ready to place some new additions or improvements, you may like to add.

Actually, using my own fork is already enough for my needs, but consistency question is very valuable for me.

Please, let me know, if you are hesitating about doing it for some reasons.

Ben Mabey
Owner

I plan on merging in the functionality. I have it merged in locally, but before I push I want to do some cleanup first (e.g. creating an easy way for people to setup the needed DBs for mysql and postgres). I think I will also change the :fast option to :pre_count or :empty_check since I think that communicates better what it is actually doing.

You can continue working off of your fork and I'll let you know when it has been merged into master along with the option name change.

Stanislaw Pankevich

Good! Thanks!

Stanislaw Pankevich

By the way, if you rewrite your README with markdown markup, you will have github-colored ruby-syntax for your examples. This is because of Github favours the usage of markdown (Github flavored markdown) instead of textile.

I've already did this painless migration from Textile to Markdown for a bunch of my gems.

Ben Mabey
Owner

Textile's table formatting is helpful for this README.. But I may still convert to markdown.

Stanislaw Pankevich

I am +1 for :empty_check as a name for the option.

Ben Mabey
Owner

Okay, I'll go with :empty_check. Thanks for the feedback!

Vito Botta

Just wanted to say thanks to bmabey for this so useful gem, and to stanislaw for his changes - I was struggling to get capybara+poltergeist to work properly with the transaction strategy and the shared connection method, due to the "This connection is still waiting for a result issue" and the tons of hacks I have tried to mitigate it.

I tried your fork and the fast truncation method, and all works great and, to my surprise, the speed is the same as with the transaction method, with the difference that I don't have to fight with the connection issues.

I have a very fast, last generation SSD though on my MBP2011. Do you think I would see different results with a normal hard drive?

Ben Mabey
Owner

Thanks for the field report @vitobotta!

WRT SSD vs harddrive... The hardrive would certainly be slower but if the whole database was small enough to fit into ram you probably wouldn't notice much of a difference.

Out of curiosity, could you share a similar report to what @stanislaw posted about his app? (Number of tables, average number of tables used per test, and then the speed for the various cleaning strategies.)

Vito Botta

The app I am working on at the moment has 30 tables, and I'd say that perhaps 3-4 is the average number of tables used per test (just guessing really, I would need to properly investigate to give an accurate figure...).

The full Cucumber suite runs in around 5'30" (5'22" right now) with the poltergeist driver for javascript scenarios, and the fast truncation strategy. With the transaction strategy and the shared connection method I save ~10 seconds but most often some scenarios fail with the "This connection is still waiting for a result" exception. I have found a few hacks that mitigate this issue by retrying the failing code or forcing reconnection and things like these, but there's no much difference after all, so for now I won't be struggling any more with trying to get the transaction strategy fully working with poltergeist (or webkit).

I've also tried again right now with Akephalos2 + transaction/shared connection method, and the full Cucumber suite ran in 5'39".

Ben Mabey
Owner

Would you mind running the suite with :truncation (no fast option) and also with :deletion?

Stanislaw Pankevich

@vitobotta, I have almost exact speedup and the similar issues with :webkit, :poltergeist (i.e. 'This connection is still waiting for result' and need for the hacks with reconnection'). I am glad you found it very useful.

Stanislaw Pankevich

Any updates on this?

Ben Mabey bmabey referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Ben Mabey bmabey referenced this pull request from a commit
Ben Mabey bmabey WIP - adds DB config and rake tasks so test DBs can be created easily
I had to bump ActiveRecord to get the "standalone_migrations" rake tasks
to work.  The task "rake db:create:all" works but the AR upgrade is
causing errors in some of the other parts of the spec suite.

I'll need to get these errors resolved before moving forward. #127
9a8b60a
Ben Mabey bmabey referenced this pull request from a commit
Ben Mabey bmabey WIP - adds DB config and rake tasks so test DBs can be created easily
I had to bump ActiveRecord to get the "standalone_migrations" rake tasks
to work.  The task "rake db:create:all" works but the AR upgrade is
causing errors in some of the other parts of the spec suite.

I'll need to get these errors resolved before moving forward. #127
9fa407b
Stanislaw Pankevich

So the last thing to be done is to rename strategy :fast => :pre_count, right? Who will do this, you or me? This time, I suggest you to do it, because I'm sure you will anyway need to do a post-work on syntax cleanups after my changes.

Ben Mabey
Owner

I'm doing this now.. I rebased to master and am doing some post-work now. Quick question.. in the MysqlAdapter you have:

def truncate_table_no_id_reset(table_name)
        rows_exist = execute("SELECT EXISTS (SELECT 1 FROM #{quote_table_name(table_name)} LIMIT 1)").fetch_row.first.to_i
        truncate_table(table_name) if rows_exist > 0
      end

(https://github.com/stanislaw/database_cleaner/blob/master/lib/database_cleaner/active_record/truncation.rb#L83)

But in the Mysql2Adapter you have:

     def truncate_table_no_id_reset(table_name)
        rows_exist = execute("SELECT EXISTS(SELECT 1 FROM #{quote_table_name(table_name)} LIMIT 1)").first.first
        truncate_table(table_name) if rows_exist == 1
      end

(https://github.com/stanislaw/database_cleaner/blob/master/lib/database_cleaner/active_record/truncation.rb#L126)

Why is the conditional different? In one you have if rows_exist > 0 and the other you have if rows_exist == 1. Seems like they could be the same, correct?

Ben Mabey
Owner

never mind... I was able to use the same code for MysqlAdapter and Mysql2Adapter and the tests passed so I don't think the difference mattered.

(commit: ca00f7d)

Stanislaw Pankevich

Yeah, you're right. They are the same.

Stanislaw Pankevich

Also, you could uncomment -1.9.3 in travis and see what happens with dependencies, when travis is doing a build for 1.9.3.

If it will require too much time to resolve it, please, let me know. I could try to help you with it, after you finish the work on fast_merge, if you would like.

I think it should be easy to set database_cleaner on travis with 1.9.3.

Ben Mabey
Owner

Sorry it took me so long to sit down and actually merge this in.. but it is done! There is some more refactoring I'd like to do (related to the per-existing monkeypatching) but I think it is best to get this in master for now. Thanks again for the patch and help!

BTW, any reason why I need the special travis rake task? Why not the default rake task?

Ben Mabey bmabey closed this
Stanislaw Pankevich

rake travis just has a subset of entire spec suite, that works for for me on travis.

As an example, I didn't touch Mongo specs at. If you manage to have a successful build with the whole suite running, it would be great.

Stanislaw Pankevich

Ah, I see, that you've done it already! Now 1.9.3 build fails because of linecache dependency - I had it failing on the same point.

By the way, I really like the way you've merged this ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
Showing with 763 additions and 135 deletions.
  1. +4 −0 .rspec
  2. +1 −1  .rvmrc
  3. +10 −0 .travis.yml
  4. +4 −1 Gemfile
  5. +65 −62 Gemfile.lock
  6. +2 −0  History.txt
  7. +2 −0  README.textile
  8. +12 −8 Rakefile
  9. +1 −1  examples/features/support/env.rb
  10. +1 −1  features/support/env.rb
  11. +135 −5 lib/database_cleaner/active_record/truncation.rb
  12. +4 −2 lib/database_cleaner/generic/truncation.rb
  13. +1 −1  spec/database_cleaner/active_record/base_spec.rb
  14. +40 −0 spec/database_cleaner/active_record/truncation/mysql2_spec.rb
  15. +40 −0 spec/database_cleaner/active_record/truncation/mysql_spec.rb
  16. +103 −0 spec/database_cleaner/active_record/truncation/postgresql_spec.rb
  17. +63 −0 spec/database_cleaner/active_record/truncation/shared_mysql.rb
  18. +89 −32 spec/database_cleaner/active_record/truncation_spec.rb
  19. +2 −2 spec/database_cleaner/configuration_spec.rb
  20. +1 −1  spec/database_cleaner/data_mapper/base_spec.rb
  21. +1 −1  spec/database_cleaner/data_mapper/transaction_spec.rb
  22. +1 −1  spec/database_cleaner/data_mapper/truncation_spec.rb
  23. +1 −1  spec/database_cleaner/generic/base_spec.rb
  24. +30 −0 spec/database_cleaner/generic/truncation_spec.rb
  25. +1 −1  spec/database_cleaner/mongo_mapper/base_spec.rb
  26. +1 −1  spec/database_cleaner/sequel/base_spec.rb
  27. +1 −1  spec/database_cleaner/sequel/transaction_spec.rb
  28. +1 −1  spec/database_cleaner/sequel/truncation_spec.rb
  29. 0  spec/database_cleaner/{shared_strategy_spec.rb → shared_strategy.rb}
  30. +0 −7 spec/spec.opts
  31. +6 −4 spec/spec_helper.rb
  32. +41 −0 spec/support/active_record/mysql2_setup.rb
  33. +41 −0 spec/support/active_record/mysql_setup.rb
  34. +45 −0 spec/support/active_record/postgresql_setup.rb
  35. +13 −0 spec/travis.rb
4 .rspec
View
@@ -0,0 +1,4 @@
+--color
+--format documentation
+mtime
+--backtrace
2  .rvmrc
View
@@ -1 +1 @@
-rvm ruby-1.8.7-p248
+rvm 1.8.7
10 .travis.yml
View
@@ -0,0 +1,10 @@
+language: ruby
+rvm:
+ - 1.8.7
+ # - 1.9.3
+script: "bundle exec rake travis"
+gemfile:
+ - Gemfile
+before_script:
+ - mysql -e 'create database database_cleaner_test;'
+ - psql -c 'create database database_cleaner_test;' -U postgres
5 Gemfile
View
@@ -34,10 +34,13 @@ group :development do
gem "couch_potato", "0.3.0"
gem "sequel", "~>3.21.0"
#gem "ibm_db" # I don't want to add this dependency, even as a dev one since it requires DB2 to be installed
+ gem 'mysql'
+ gem 'mysql2', '~> 0.2.0'
+ gem 'pg'
end
group :test do
- gem "rspec"
+ gem "rspec-rails"
gem "rspactor"
gem "rcov"
gem "ZenTest"
127 Gemfile.lock
View
@@ -1,28 +1,27 @@
GEM
remote: http://rubygems.org/
specs:
- ZenTest (4.3.3)
+ ZenTest (4.8.1)
activerecord (2.3.8)
activesupport (= 2.3.8)
activesupport (2.3.8)
- addressable (2.2.0)
- bson (1.0.4)
- builder (2.1.2)
- columnize (0.3.1)
+ addressable (2.2.8)
+ bson (1.0.9)
+ builder (3.0.0)
+ columnize (0.3.6)
couch_potato (0.3.0)
couchrest (>= 0.24)
json
- couchrest (1.0.1)
+ couchrest (1.1.2)
+ mime-types (~> 1.15)
+ multi_json (~> 1.0.0)
+ rest-client (~> 1.6.1)
+ cucumber (1.2.1)
+ builder (>= 2.1.2)
+ diff-lcs (>= 1.1.3)
+ gherkin (~> 2.11.0)
json (>= 1.4.6)
- mime-types (>= 1.15)
- rest-client (>= 1.5.1)
- cucumber (0.8.5)
- builder (~> 2.1.2)
- diff-lcs (~> 1.1.2)
- gherkin (~> 2.1.4)
- json_pure (~> 1.4.3)
- term-ansicolor (~> 1.0.4)
- data_objects (0.10.2)
+ data_objects (0.10.8)
addressable (~> 2.1)
datamapper (1.0.0)
dm-aggregates (= 1.0.0)
@@ -35,7 +34,7 @@ GEM
dm-transactions (= 1.0.0)
dm-types (= 1.0.0)
dm-validations (= 1.0.0)
- diff-lcs (1.1.2)
+ diff-lcs (1.1.3)
dm-aggregates (1.0.0)
dm-core (~> 1.0.0)
dm-constraints (1.0.0)
@@ -68,32 +67,28 @@ GEM
uuidtools (~> 2.1.1)
dm-validations (1.0.0)
dm-core (~> 1.0.0)
- do_sqlite3 (0.10.2)
- data_objects (= 0.10.2)
+ do_sqlite3 (0.10.8)
+ data_objects (= 0.10.8)
durran-validatable (2.0.1)
extlib (0.9.15)
- fastercsv (1.5.3)
- ffi (0.6.3)
- rake (>= 0.8.7)
- gemcutter (0.6.1)
- gherkin (2.1.5)
- trollop (~> 1.16.2)
+ fastercsv (1.5.5)
+ gherkin (2.11.1)
+ json (>= 1.4.6)
git (1.2.5)
- growl (1.0.3)
- jeweler (1.4.0)
- gemcutter (>= 0.1.0)
+ jeweler (1.8.4)
+ bundler (~> 1.0)
git (>= 1.2.5)
- rubyforge (>= 2.0.0)
+ rake
+ rdoc
jnunemaker-validatable (1.8.4)
activesupport (>= 2.3.4)
- json (1.4.6)
+ json (1.7.3)
json_pure (1.4.6)
- libnotify (0.2.0)
- ffi (>= 0.6.2)
- linecache (0.43)
- mime-types (1.16)
- mongo (1.0.7)
- bson (>= 1.0.4)
+ linecache (0.46)
+ rbx-require-relative (> 0.0.4)
+ mime-types (1.19)
+ mongo (1.0.9)
+ bson (>= 1.0.5)
mongo_mapper (0.8.2)
activesupport (>= 2.3.4)
jnunemaker-validatable (~> 1.8.4)
@@ -104,38 +99,43 @@ GEM
durran-validatable (>= 2.0.1)
mongo (~> 1.0.1)
will_paginate (< 2.9)
- plucky (0.3.4)
- mongo (~> 1.0.7)
- rake (0.8.7)
- rb-inotify (0.8.1)
- ffi (>= 0.5.0)
- rcov (0.9.8)
- rest-client (1.6.0)
+ multi_json (1.0.4)
+ mysql (2.8.1)
+ mysql2 (0.2.18)
+ pg (0.14.0)
+ plucky (0.3.5)
+ mongo (~> 1.0.8)
+ rake (0.9.2.2)
+ rbx-require-relative (0.0.9)
+ rcov (1.0.0)
+ rdoc (3.12)
+ json (~> 1.4)
+ rest-client (1.6.7)
mime-types (>= 1.16)
- rspactor (0.7.0.beta.6)
- bundler (>= 1.0.0.rc.5)
- growl (>= 1.0.3)
- libnotify (>= 0.1.3)
- rb-inotify
- sys-uname (>= 0.8.4)
- trollop (>= 1.16.2)
- rspec (1.3.0)
- ruby-debug (0.10.3)
+ rspactor (0.6.4)
+ rspec (2.1.0)
+ rspec-core (~> 2.1.0)
+ rspec-expectations (~> 2.1.0)
+ rspec-mocks (~> 2.1.0)
+ rspec-core (2.1.0)
+ rspec-expectations (2.1.0)
+ diff-lcs (~> 1.1.2)
+ rspec-mocks (2.1.0)
+ rspec-rails (2.1.0)
+ rspec (~> 2.1.0)
+ ruby-debug (0.10.4)
columnize (>= 0.1)
- ruby-debug-base (~> 0.10.3.0)
- ruby-debug-base (0.10.3)
+ ruby-debug-base (~> 0.10.4.0)
+ ruby-debug-base (0.10.4)
linecache (>= 0.3)
- rubyforge (2.0.4)
- json_pure (>= 1.1.7)
sequel (3.21.0)
- sqlite3-ruby (1.3.1)
+ sqlite3 (1.3.6)
+ sqlite3-ruby (1.3.3)
+ sqlite3 (>= 1.3.3)
stringex (1.1.0)
- sys-uname (0.8.4)
- term-ansicolor (1.0.5)
- trollop (1.16.2)
tzinfo (0.3.22)
- uuidtools (2.1.1)
- will_paginate (2.3.14)
+ uuidtools (2.1.2)
+ will_paginate (2.3.16)
PLATFORMS
ruby
@@ -153,10 +153,13 @@ DEPENDENCIES
json_pure
mongo_mapper (= 0.8.2)
mongoid (= 1.9.1)
+ mysql
+ mysql2 (~> 0.2.0)
+ pg
rake
rcov
rspactor
- rspec
+ rspec-rails
ruby-debug
sequel (~> 3.21.0)
sqlite3-ruby
2  History.txt
View
@@ -2,6 +2,8 @@
== 0.8.0 (in git)
+ * Faster truncation strategy for ActiveRecord with MySQL or PostgreSQL
+ * Upgrade to RSpec 2
* Support for Mongoid 3/Moped (Andrew Bennett)
== 0.7.2 2012-03-21
2  README.textile
View
@@ -7,6 +7,8 @@ that is testing with a database.
ActiveRecord, DataMapper, Sequel, MongoMapper, Mongoid, and CouchPotato are supported.
+!https://secure.travis-ci.org/bmabey/database_cleaner.png(Build Status)!:http://travis-ci.org/stanislaw/database_cleaner
+
Here is an overview of the strategies supported for each library:
|_. ORM |_. Truncation |_. Transaction |_. Deletion |
20 Rakefile
View
@@ -28,16 +28,20 @@ Rake::RDocTask.new do |rdoc|
rdoc.rdoc_files.include('lib/**/*.rb')
end
-require 'spec/rake/spectask'
-Spec::Rake::SpecTask.new(:spec) do |t|
- t.libs << 'lib' << 'spec'
- t.spec_files = FileList['spec/**/*_spec.rb']
+
+require 'rspec/core'
+require 'rspec/core/rake_task'
+RSpec::Core::RakeTask.new(:spec) do |spec|
+ spec.pattern = FileList['spec/**/*_spec.rb']
+end
+
+RSpec::Core::RakeTask.new(:travis) do |spec|
+ spec.pattern = FileList['spec/travis.rb']
end
-Spec::Rake::SpecTask.new(:rcov) do |t|
- t.libs << 'lib' << 'spec'
- t.spec_files = FileList['spec/**/*_spec.rb']
- t.rcov = true
+RSpec::Core::RakeTask.new(:rcov) do |spec|
+ spec.pattern = 'spec/**/*_spec.rb'
+ spec.rcov = true
end
begin
2  examples/features/support/env.rb
View
@@ -5,7 +5,7 @@
require 'bundler'
Bundler.setup
-require 'spec/expectations'
+require 'rspec/expectations'
require 'ruby-debug'
DB_DIR = "#{File.dirname(__FILE__)}/../../db"
2  features/support/env.rb
View
@@ -1,7 +1,7 @@
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
require 'database_cleaner'
-require 'spec/expectations'
+require 'rspec/expectations'
require 'test/unit/assertions'
140 lib/database_cleaner/active_record/truncation.rb
View
@@ -45,12 +45,86 @@ class MysqlAdapter < MYSQL_ADAPTER_PARENT
def truncate_table(table_name)
execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
end
+
+ def fast_truncate_tables *tables_and_opts
+ opts = tables_and_opts.last.is_a?(::Hash) ? tables_and_opts.pop : {}
+ reset_ids = opts[:reset_ids] != false
+
+ _tables = tables_and_opts.flatten
+
+ _tables.each do |table_name|
+ if reset_ids
+ truncate_table_with_id_reset(table_name)
+ else
+ truncate_table_no_id_reset(table_name)
+ end
+ end
+ end
+
+ def truncate_table_with_id_reset(table_name)
+ rows_exist = execute("SELECT EXISTS(SELECT 1 FROM #{quote_table_name(table_name)} LIMIT 1)").fetch_row.first.to_i
+
+ if rows_exist == 0
+ auto_inc = execute(<<-AUTO_INCREMENT
+ SELECT Auto_increment
+ FROM information_schema.tables
+ WHERE table_name='#{table_name}';
+ AUTO_INCREMENT
+ )
+
+ truncate_table(table_name) if auto_inc.fetch_row.first.to_i > 1
+ else
+ truncate_table(table_name)
+ end
+ end
+
+ def truncate_table_no_id_reset(table_name)
+ rows_exist = execute("SELECT EXISTS (SELECT 1 FROM #{quote_table_name(table_name)} LIMIT 1)").fetch_row.first.to_i
+ truncate_table(table_name) if rows_exist > 0
+ end
end
class Mysql2Adapter < MYSQL2_ADAPTER_PARENT
def truncate_table(table_name)
execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
end
+
+ def fast_truncate_tables *tables_and_opts
+ opts = tables_and_opts.last.is_a?(::Hash) ? tables_and_opts.pop : {}
+ reset_ids = opts[:reset_ids] != false
+
+ _tables = tables_and_opts.flatten
+
+ _tables.each do |table_name|
+ if reset_ids
+ truncate_table_with_id_reset(table_name)
+ else
+ truncate_table_no_id_reset(table_name)
+ end
+ end
+ end
+
+ def truncate_table_with_id_reset(table_name)
+ rows_exist = execute("SELECT EXISTS(SELECT 1 FROM #{quote_table_name(table_name)} LIMIT 1)").first.first.to_i
+
+ if rows_exist == 0
+ auto_inc = execute(<<-AUTO_INCREMENT
+ SELECT Auto_increment
+ FROM information_schema.tables
+ WHERE table_name='#{table_name}';
+ AUTO_INCREMENT
+ )
+
+ truncate_table(table_name) if auto_inc.first.first.to_i > 1
+ else
+ truncate_table(table_name)
+ end
+ end
+
+ def truncate_table_no_id_reset(table_name)
+ rows_exist = execute("SELECT EXISTS(SELECT 1 FROM #{quote_table_name(table_name)} LIMIT 1)").first.first
+ truncate_table(table_name) if rows_exist == 1
+ end
end
class IBM_DBAdapter < AbstractAdapter
@@ -94,12 +168,60 @@ def restart_identity
def truncate_table(table_name)
truncate_tables([table_name])
end
-
+
def truncate_tables(table_names)
return if table_names.nil? || table_names.empty?
execute("TRUNCATE TABLE #{table_names.map{|name| quote_table_name(name)}.join(', ')} #{restart_identity} #{cascade};")
end
+ def fast_truncate_tables(*tables_and_opts)
+ opts = tables_and_opts.last.is_a?(::Hash) ? tables_and_opts.pop : {}
+ reset_ids = opts[:reset_ids] != false
+
+ _tables = tables_and_opts.flatten
+
+ if reset_ids
+ truncate_tables_with_id_reset(_tables)
+ else
+ truncate_tables_no_id_reset(_tables)
+ end
+ end
+
+ def truncate_tables_with_id_reset(_tables)
+ tables_to_truncate = []
+
+ _tables.each do |table|
+ begin
+ table_curr_value = execute(<<-CURR_VAL
+ SELECT currval('#{table}_id_seq');
+ CURR_VAL
+ ).first['currval'].to_i
+ rescue ActiveRecord::StatementInvalid
+ table_curr_value = nil
+ end
+
+ if table_curr_value && table_curr_value > 0
+ tables_to_truncate << table
+ end
+ end
+
+ truncate_tables(tables_to_truncate) if tables_to_truncate.any?
+ end
+
+ def truncate_tables_no_id_reset(_tables)
+ tables_to_truncate = []
+
+ _tables.each do |table|
+ rows_exist = execute(<<-TR
+ SELECT true FROM #{table} LIMIT 1;
+ TR
+ )
+
+ tables_to_truncate << table if rows_exist.any?
+ end
+
+ truncate_tables(tables_to_truncate) if tables_to_truncate.any?
+ end
end
class SQLServerAdapter < AbstractAdapter
@@ -130,7 +252,11 @@ class Truncation
def clean
connection = connection_klass.connection
connection.disable_referential_integrity do
- connection.truncate_tables(tables_to_truncate(connection))
+ if fast? && connection.respond_to?(:fast_truncate_tables)
+ connection.fast_truncate_tables(tables_to_truncate(connection), {:reset_ids => reset_ids?})
+ else
+ connection.truncate_tables(tables_to_truncate(connection))
+ end
end
end
@@ -145,8 +271,12 @@ def migration_storage_name
'schema_migrations'
end
+ def fast?
+ @fast == true
+ end
+
+ def reset_ids?
+ @reset_ids != false
+ end
end
end
-
-
-
6 lib/database_cleaner/generic/truncation.rb
View
@@ -2,8 +2,8 @@ module DatabaseCleaner
module Generic
module Truncation
def initialize(opts={})
- if !opts.empty? && !(opts.keys - [:only, :except]).empty?
- raise ArgumentError, "The only valid options are :only and :except. You specified #{opts.keys.join(',')}."
+ if !opts.empty? && !(opts.keys - [:only, :except, :fast, :reset_ids]).empty?
+ raise ArgumentError, "The only valid options are :only, :except, :fast or :reset_ids. You specified #{opts.keys.join(',')}."
end
if opts.has_key?(:only) && opts.has_key?(:except)
raise ArgumentError, "You may only specify either :only or :except. Doing both doesn't really make sense does it?"
@@ -12,6 +12,8 @@ def initialize(opts={})
@only = opts[:only]
@tables_to_exclude = (opts[:except] || []).dup
@tables_to_exclude << migration_storage_name if migration_storage_name
+ @fast = opts[:fast]
+ @reset_ids = opts[:reset_ids]
end
def start
2  spec/database_cleaner/active_record/base_spec.rb
View
@@ -1,7 +1,7 @@
require 'spec_helper'
require 'active_record'
require 'database_cleaner/active_record/base'
-require 'database_cleaner/shared_strategy_spec'
+require 'database_cleaner/shared_strategy'
module DatabaseCleaner
describe ActiveRecord do
40 spec/database_cleaner/active_record/truncation/mysql2_spec.rb
View
@@ -0,0 +1,40 @@
+require 'spec_helper'
+require 'active_record'
+require 'support/active_record/mysql2_setup'
+require 'database_cleaner/active_record/truncation'
+require 'database_cleaner/active_record/truncation/shared_mysql'
+
+module ActiveRecord
+ module ConnectionAdapters
+ describe do
+ before(:all) { active_record_mysql2_setup }
+
+ let(:adapter) { Mysql2Adapter }
+ let(:connection) { active_record_mysql2_connection }
+
+ describe "#truncate_table" do
+ it "should truncate the table" do
+ 2.times { User.create }
+
+ connection.truncate_table('users')
+ User.count.should == 0
+ end
+
+ it "should reset AUTO_INCREMENT index of table" do
+ 2.times { User.create }
+ User.delete_all
+
+ connection.truncate_table('users')
+
+ User.create.id.should == 1
+ end
+ end
+
+ it_behaves_like "Fast truncation" do
+ let(:adapter) { Mysql2Adapter }
+ let(:connection) { active_record_mysql2_connection }
+ end
+ end
+ end
+end
+
40 spec/database_cleaner/active_record/truncation/mysql_spec.rb
View
@@ -0,0 +1,40 @@
+require 'spec_helper'
+require 'active_record'
+require 'support/active_record/mysql_setup'
+require 'database_cleaner/active_record/truncation'
+require 'database_cleaner/active_record/truncation/shared_mysql'
+
+module ActiveRecord
+ module ConnectionAdapters
+ describe do
+ before(:all) { active_record_mysql_setup }
+
+ let(:adapter) { MysqlAdapter }
+ let(:connection) { active_record_mysql_connection }
+
+ describe "#truncate_table" do
+ it "should truncate the table" do
+ 2.times { User.create }
+
+ connection.truncate_table('users')
+ User.count.should == 0
+ end
+
+ it "should reset AUTO_INCREMENT index of table" do
+ 2.times { User.create }
+ User.delete_all
+
+ connection.truncate_table('users')
+
+ User.create.id.should == 1
+ end
+ end
+
+ it_behaves_like "Fast truncation" do
+ let(:adapter) { MysqlAdapter }
+ let(:connection) { active_record_mysql_connection }
+ end
+ end
+ end
+end
+
103 spec/database_cleaner/active_record/truncation/postgresql_spec.rb
View
@@ -0,0 +1,103 @@
+require 'spec_helper'
+require 'active_record'
+require 'support/active_record/postgresql_setup'
+require 'database_cleaner/active_record/truncation'
+
+module ActiveRecord
+ module ConnectionAdapters
+ describe do
+ before(:all) { active_record_pg_setup }
+
+ let(:adapter) { PostgreSQLAdapter }
+
+ let(:connection) do
+ active_record_pg_connection
+ end
+
+ before(:each) do
+ connection.truncate_tables connection.tables
+ end
+
+ describe "#truncate_table" do
+ it "should truncate the table" do
+ 2.times { User.create }
+
+ connection.truncate_table('users')
+ User.count.should == 0
+ end
+
+ it "should reset AUTO_INCREMENT index of table" do
+ 2.times { User.create }
+ User.delete_all
+
+ connection.truncate_table('users')
+
+ User.create.id.should == 1
+ end
+ end
+
+ describe "#truncate_tables_with_id_reset" do
+ it "responds" do
+ adapter.instance_methods.should include('truncate_tables_with_id_reset')
+ end
+
+ it "should truncate the table" do
+ 2.times { User.create }
+
+ connection.truncate_tables_with_id_reset('users')
+ User.count.should == 0
+ end
+
+ it "should reset AUTO_INCREMENT index of table" do
+ 2.times { User.create }
+ User.delete_all
+
+ connection.truncate_tables_with_id_reset('users')
+
+ User.create.id.should == 1
+ end
+ end
+
+ describe "#truncate_tables_no_id_reset" do
+ it "responds" do
+ adapter.instance_methods.map(&:to_s).should include('truncate_tables_no_id_reset')
+ end
+
+ it "should truncate the table" do
+ 2.times { User.create }
+
+ connection.truncate_tables_no_id_reset('users')
+ User.count.should == 0
+ end
+
+ it "should not reset AUTO_INCREMENT index of table" do
+ 2.times { User.create }
+ User.delete_all
+
+ connection.truncate_tables_no_id_reset('users')
+
+ User.create.id.should == 3
+ end
+ end
+
+ describe "#fast_truncate_tables" do
+ it "responds" do
+ adapter.instance_methods.should include('fast_truncate_tables')
+ end
+
+ it 'should call #truncate_tables_with_id_reset on each table if :reset_ids option true was given' do
+ connection.should_receive(:truncate_tables_with_id_reset).exactly(connection.tables.size).times
+
+ connection.fast_truncate_tables(connection.tables)
+ end
+
+ it 'should call #truncate_tables_with_id_reset on each table if :reset_ids option false was given' do
+ connection.should_receive(:truncate_tables_no_id_reset).exactly(connection.tables.size).times
+
+ connection.fast_truncate_tables(connection.tables, :reset_ids => false)
+ end
+ end
+ end
+ end
+end
+
63 spec/database_cleaner/active_record/truncation/shared_mysql.rb
View
@@ -0,0 +1,63 @@
+shared_examples_for "Fast truncation" do
+ describe "#truncate_table_with_id_reset" do
+ it "responds" do
+ adapter.instance_methods.should include('truncate_table_with_id_reset')
+ end
+
+ it "should truncate the table" do
+ 2.times { User.create }
+
+ connection.truncate_table_with_id_reset('users')
+ User.count.should == 0
+ end
+
+ it "should reset AUTO_INCREMENT index of table" do
+ 2.times { User.create }
+ User.delete_all
+
+ connection.truncate_table_with_id_reset('users')
+
+ User.create.id.should == 1
+ end
+ end
+
+ describe "#truncate_table_no_id_reset" do
+ it "responds" do
+ adapter.instance_methods.map(&:to_s).should include('truncate_table_no_id_reset')
+ end
+
+ it "should truncate the table" do
+ 2.times { User.create }
+
+ connection.truncate_table_no_id_reset('users')
+ User.count.should == 0
+ end
+
+ it "should not reset AUTO_INCREMENT index of table" do
+ 2.times { User.create }
+ User.delete_all
+
+ connection.truncate_table_no_id_reset('users')
+
+ User.create.id.should == 3
+ end
+ end
+
+ describe "#fast_truncate_tables" do
+ it "responds" do
+ adapter.instance_methods.should include('fast_truncate_tables')
+ end
+
+ it 'should call #truncate_table_with_id_reset on each table if :reset_ids option true was given' do
+ connection.should_receive(:truncate_table_with_id_reset).exactly(connection.tables.size).times
+
+ connection.fast_truncate_tables(connection.tables)
+ end
+
+ it 'should call #truncate_table_with_id_reset on each table if :reset_ids option false was given' do
+ connection.should_receive(:truncate_table_no_id_reset).exactly(connection.tables.size).times
+
+ connection.fast_truncate_tables(connection.tables, :reset_ids => false)
+ end
+ end
+end
121 spec/database_cleaner/active_record/truncation_spec.rb
View
@@ -1,16 +1,15 @@
require File.dirname(__FILE__) + '/../../spec_helper'
require 'active_record'
-require 'database_cleaner/active_record/truncation'
+require 'database_cleaner/active_record/truncation'
module ActiveRecord
module ConnectionAdapters
[MysqlAdapter, Mysql2Adapter, SQLite3Adapter, JdbcAdapter, PostgreSQLAdapter, IBM_DBAdapter].each do |adapter|
describe adapter, "#truncate_table" do
it "responds" do
- adapter.new("foo").should respond_to(:truncate_table)
+ adapter.instance_methods.should include('truncate_table')
end
- it "should truncate the table"
end
end
end
@@ -22,55 +21,113 @@ module ActiveRecord
describe Truncation do
let(:connection) { mock('connection') }
-
before(:each) do
connection.stub!(:disable_referential_integrity).and_yield
connection.stub!(:views).and_return([])
::ActiveRecord::Base.stub!(:connection).and_return(connection)
end
- it "should truncate all tables except for schema_migrations" do
- connection.stub!(:tables).and_return(%w[schema_migrations widgets dogs])
-
- connection.should_receive(:truncate_tables).with(['widgets', 'dogs'])
- Truncation.new.clean
- end
+ describe '#clean' do
+ it "should truncate all tables except for schema_migrations" do
+ connection.stub!(:tables).and_return(%w[schema_migrations widgets dogs])
- it "should only truncate the tables specified in the :only option when provided" do
- connection.stub!(:tables).and_return(%w[schema_migrations widgets dogs])
+ connection.should_receive(:truncate_tables).with(['widgets', 'dogs'])
+ Truncation.new.clean
+ end
- connection.should_receive(:truncate_tables).with(['widgets'])
+ it "should only truncate the tables specified in the :only option when provided" do
+ connection.stub!(:tables).and_return(%w[schema_migrations widgets dogs])
- Truncation.new(:only => ['widgets']).clean
- end
+ connection.should_receive(:truncate_tables).with(['widgets'])
- it "should not truncate the tables specified in the :except option" do
- connection.stub!(:tables).and_return(%w[schema_migrations widgets dogs])
+ Truncation.new(:only => ['widgets']).clean
+ end
- connection.should_receive(:truncate_tables).with(['dogs'])
+ it "should not truncate the tables specified in the :except option" do
+ connection.stub!(:tables).and_return(%w[schema_migrations widgets dogs])
- Truncation.new(:except => ['widgets']).clean
- end
+ connection.should_receive(:truncate_tables).with(['dogs'])
- it "should raise an error when :only and :except options are used" do
- running {
- Truncation.new(:except => ['widgets'], :only => ['widgets'])
- }.should raise_error(ArgumentError)
- end
+ Truncation.new(:except => ['widgets']).clean
+ end
+
+ it "should raise an error when :only and :except options are used" do
+ running {
+ Truncation.new(:except => ['widgets'], :only => ['widgets'])
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should raise an error when invalid options are provided" do
+ running { Truncation.new(:foo => 'bar') }.should raise_error(ArgumentError)
+ end
+
+ it "should not truncate views" do
+ connection.stub!(:tables).and_return(%w[widgets dogs])
+ connection.stub!(:views).and_return(["widgets"])
+
+ connection.should_receive(:truncate_tables).with(['dogs'])
- it "should raise an error when invalid options are provided" do
- running { Truncation.new(:foo => 'bar') }.should raise_error(ArgumentError)
+ Truncation.new.clean
+ end
+
+ describe "relying on #fast_truncate_tables if connection allows it" do
+ subject { Truncation.new }
+
+ it "should rely on #fast_truncate_tables if #fast? returns true" do
+ connection.stub!(:tables).and_return(%w[widgets dogs])
+ connection.stub!(:views).and_return(["widgets"])
+
+ subject.instance_variable_set(:"@fast", true)
+
+ connection.should_not_receive(:truncate_tables).with(['dogs'])
+ connection.should_receive(:fast_truncate_tables).with(['dogs'], :reset_ids => true)
+
+ subject.clean
+ end
+
+ it "should not rely on #fast_truncate_tables if #fast? return false" do
+ connection.stub!(:tables).and_return(%w[widgets dogs])
+ connection.stub!(:views).and_return(["widgets"])
+
+ subject.instance_variable_set(:"@fast", false)
+
+ connection.should_not_receive(:fast_truncate_tables).with(['dogs'], :reset_ids => true)
+ connection.should_receive(:truncate_tables).with(['dogs'])
+
+ subject.clean
+ end
+ end
end
- it "should not truncate views" do
- connection.stub!(:tables).and_return(%w[widgets dogs])
- connection.stub!(:views).and_return(["widgets"])
+ describe '#fast?' do
+ subject { Truncation.new }
+ its(:fast?) { should == false }
- connection.should_receive(:truncate_tables).with(['dogs'])
+ it 'should return true if @reset_id is set and non false or nil' do
+ subject.instance_variable_set(:"@fast", true)
+ subject.send(:fast?).should == true
+ end
- Truncation.new.clean
+ it 'should return false if @reset_id is set to false' do
+ subject.instance_variable_set(:"@fast", false)
+ subject.send(:fast?).should == false
+ end
end
+
+ describe '#reset_ids?' do
+ subject { Truncation.new }
+ its(:reset_ids?) { should == true }
+
+ it 'should return true if @reset_id is set and non false or nil' do
+ subject.instance_variable_set(:"@reset_ids", 'Something')
+ subject.send(:reset_ids?).should == true
+ end
+ it 'should return false if @reset_id is set to false' do
+ subject.instance_variable_set(:"@reset_ids", false)
+ subject.send(:reset_ids?).should == false
+ end
+ end
end
end
end
4 spec/database_cleaner/configuration_spec.rb
View
@@ -1,4 +1,4 @@
-require File.dirname(__FILE__) + '/../spec_helper'
+require 'spec_helper'
module DatabaseCleaner
class << self
@@ -197,7 +197,7 @@ def connections_stub!(array)
# plausably want to force orm/strategy change on two sets of orm that differ only on db
context "multiple orm proxy methods" do
- it "should proxy orm to all connections and remove duplicate connections" do
+ pending "should proxy orm to all connections and remove duplicate connections" do
active_record_1 = mock("active_mock_on_db_one").as_null_object
active_record_2 = mock("active_mock_on_db_two").as_null_object
data_mapper_1 = mock("data_mock_on_db_one").as_null_object
2  spec/database_cleaner/data_mapper/base_spec.rb
View
@@ -1,6 +1,6 @@
require 'spec_helper'
require 'database_cleaner/data_mapper/base'
-require 'database_cleaner/shared_strategy_spec'
+require 'database_cleaner/shared_strategy'
module DatabaseCleaner
describe DataMapper do
2  spec/database_cleaner/data_mapper/transaction_spec.rb
View
@@ -1,6 +1,6 @@
require File.dirname(__FILE__) + '/../../spec_helper'
require 'database_cleaner/data_mapper/transaction'
-require 'database_cleaner/shared_strategy_spec'
+require 'database_cleaner/shared_strategy'
#require 'data_mapper'
module DatabaseCleaner
2  spec/database_cleaner/data_mapper/truncation_spec.rb
View
@@ -1,5 +1,5 @@
require 'database_cleaner/data_mapper/truncation'
-require 'database_cleaner/shared_strategy_spec'
+require 'database_cleaner/shared_strategy'
module DatabaseCleaner
module DataMapper
2  spec/database_cleaner/generic/base_spec.rb
View
@@ -1,5 +1,5 @@
require 'spec_helper'
-require 'database_cleaner/shared_strategy_spec'
+require 'database_cleaner/shared_strategy'
require 'database_cleaner/generic/base'
module ::DatabaseCleaner
30 spec/database_cleaner/generic/truncation_spec.rb
View
@@ -13,6 +13,14 @@ def only
def except
@tables_to_exclude
end
+
+ def reset_ids?
+ !!@reset_ids
+ end
+
+ def fast?
+ !!@fast
+ end
end
class MigrationExample < TruncationExample
@@ -44,6 +52,8 @@ def migration_storage_name
it { expect{ TruncationExample.new( { :except => "something",:only => "something else" } ) }.to raise_error(ArgumentError) }
it { expect{ TruncationExample.new( { :only => "something" } ) }.to_not raise_error(ArgumentError) }
it { expect{ TruncationExample.new( { :except => "something" } ) }.to_not raise_error(ArgumentError) }
+ it { expect{ TruncationExample.new( { :fast => "something" } ) }.to_not raise_error(ArgumentError) }
+ it { expect{ TruncationExample.new( { :reset_ids => "something" } ) }.to_not raise_error(ArgumentError) }
context "" do
subject { TruncationExample.new( { :only => ["something"] } ) }
@@ -58,6 +68,26 @@ def migration_storage_name
end
context "" do
+ subject { TruncationExample.new( { :reset_ids => ["something"] } ) }
+ its(:reset_ids?) { should == true }
+ end
+
+ context "" do
+ subject { TruncationExample.new( { :reset_ids => nil } ) }
+ its(:reset_ids?) { should == false }
+ end
+
+ context "" do
+ subject { TruncationExample.new( { :fast => ["something"] } ) }
+ its(:fast?) { should == true }
+ end
+
+ context "" do
+ subject { TruncationExample.new( { :fast => nil } ) }
+ its(:fast?) { should == false }
+ end
+
+ context "" do
subject { MigrationExample.new }
its(:only) { should == nil }
its(:except) { should == ["migration_storage_name"] }
2  spec/database_cleaner/mongo_mapper/base_spec.rb
View
@@ -1,6 +1,6 @@
require 'spec_helper'
require 'database_cleaner/mongo_mapper/base'
-require 'database_cleaner/shared_strategy_spec'
+require 'database_cleaner/shared_strategy'
module DatabaseCleaner
describe MongoMapper do
2  spec/database_cleaner/sequel/base_spec.rb
View
@@ -1,6 +1,6 @@
require 'spec_helper'
require 'database_cleaner/sequel/base'
-require 'database_cleaner/shared_strategy_spec'
+require 'database_cleaner/shared_strategy'
require 'sequel'
module DatabaseCleaner
2  spec/database_cleaner/sequel/transaction_spec.rb
View
@@ -1,6 +1,6 @@
require 'spec_helper'
require 'database_cleaner/sequel/transaction'
-require 'database_cleaner/shared_strategy_spec'
+require 'database_cleaner/shared_strategy'
require 'sequel'
module DatabaseCleaner
2  spec/database_cleaner/sequel/truncation_spec.rb
View
@@ -1,6 +1,6 @@
require 'spec_helper'
require 'database_cleaner/sequel/truncation'
-require 'database_cleaner/shared_strategy_spec'
+require 'database_cleaner/shared_strategy'
require 'sequel'
module DatabaseCleaner
0  spec/database_cleaner/shared_strategy_spec.rb → spec/database_cleaner/shared_strategy.rb
View
File renamed without changes
7 spec/spec.opts
View
@@ -1,7 +0,0 @@
---colour
---format nested
---loadby
-mtime
---reverse
---backtrace
---debugger
10 spec/spec_helper.rb
View
@@ -3,16 +3,18 @@
require "bundler"
Bundler.setup
+require 'rspec/core'
+require 'rspec/mocks'
-require 'spec'
#require 'active_record'
#require 'mongo_mapper'
-$:.unshift(File.dirname(__FILE__) + '/../lib')
-require 'database_cleaner'
+$:.unshift(File.dirname(__FILE__))
+$:.unshift(File.dirname(__FILE__) + '/../lib')
+require 'database_cleaner'
-Spec::Runner.configure do |config|
+RSpec.configure do |config|
end
41 spec/support/active_record/mysql2_setup.rb
View
@@ -0,0 +1,41 @@
+module MySQL2Helper
+ puts "Active Record #{ActiveRecord::VERSION::STRING}, mysql2"
+
+ # ActiveRecord::Base.logger = Logger.new(STDERR)
+
+ @@mysql2_db_spec = {
+ :adapter => 'mysql2',
+ :host => 'localhost',
+ :username => 'root',
+ :password => '',
+ :encoding => 'utf8'
+ }
+
+ @@db = {:database => 'database_cleaner_test'}
+
+ def active_record_mysql2_setup
+ ActiveRecord::Base.establish_connection(@@mysql2_db_spec)
+
+ ActiveRecord::Base.connection.drop_database @@db[:database] rescue nil
+ ActiveRecord::Base.connection.create_database @@db[:database]
+
+ ActiveRecord::Base.establish_connection(@@mysql2_db_spec.merge(@@db))
+
+ ActiveRecord::Schema.define do
+ create_table :users, :force => true do |t|
+ t.integer :name
+ end
+ end
+ end
+
+ def active_record_mysql2_connection
+ ActiveRecord::Base.connection
+ end
+
+ class ::User < ActiveRecord::Base
+ end
+end
+
+RSpec.configure do |c|
+ c.include MySQL2Helper
+end
41 spec/support/active_record/mysql_setup.rb
View
@@ -0,0 +1,41 @@
+module MySQLHelper
+ puts "Active Record #{ActiveRecord::VERSION::STRING}, mysql"
+
+ # ActiveRecord::Base.logger = Logger.new(STDERR)
+
+ @@mysql_db_spec = {
+ :adapter => 'mysql',
+ :host => 'localhost',
+ :username => 'root',
+ :password => '',
+ :encoding => 'utf8'
+ }
+
+ @@db = {:database => 'database_cleaner_test'}
+
+ def active_record_mysql_setup
+ ActiveRecord::Base.establish_connection(@@mysql_db_spec)
+
+ ActiveRecord::Base.connection.drop_database @@db[:database] rescue nil
+ ActiveRecord::Base.connection.create_database @@db[:database]
+
+ ActiveRecord::Base.establish_connection(@@mysql_db_spec.merge(@@db))
+
+ ActiveRecord::Schema.define do
+ create_table :users, :force => true do |t|
+ t.integer :name
+ end
+ end
+ end
+
+ def active_record_mysql_connection
+ ActiveRecord::Base.connection
+ end
+
+ class ::User < ActiveRecord::Base
+ end
+end
+
+RSpec.configure do |c|
+ c.include MySQLHelper
+end
45 spec/support/active_record/postgresql_setup.rb
View
@@ -0,0 +1,45 @@
+module PostgreSQLHelper
+ puts "Active Record #{ActiveRecord::VERSION::STRING}, pg"
+
+ # ActiveRecord::Base.logger = Logger.new(STDERR)
+
+ # createdb database_cleaner_test -E UTF8 -T template0
+
+ @@pg_db_spec = {
+ :adapter => 'postgresql',
+ :database => 'postgres',
+ :host => '127.0.0.1',
+ :username => 'postgres',
+ :password => '',
+ :encoding => 'unicode',
+ :template => 'template0'
+ }
+
+ @@db = {:database => 'database_cleaner_test'}
+
+ # ActiveRecord::Base.establish_connection(@@pg_db_spec)
+
+ # ActiveRecord::Base.connection.drop_database db[:database] rescue nil
+ # ActiveRecord::Base.connection.create_database db[:database]
+
+ def active_record_pg_setup
+ ActiveRecord::Base.establish_connection(@@pg_db_spec.merge(@@db))
+
+ ActiveRecord::Schema.define do
+ create_table :users, :force => true do |t|
+ t.integer :name
+ end
+ end
+ end
+
+ def active_record_pg_connection
+ ActiveRecord::Base.connection
+ end
+
+ class ::User < ActiveRecord::Base
+ end
+end
+
+RSpec.configure do |c|
+ c.include PostgreSQLHelper
+end
13 spec/travis.rb
View
@@ -0,0 +1,13 @@
+require 'spec_helper'
+
+generic_specs = Dir[
+ 'spec/base_spec.rb',
+ 'spec/configuration_spec.rb',
+ 'spec/database_cleaner/generic/**/*_spec.rb'
+]
+
+active_record_specs = Dir[
+ 'spec/database_cleaner/active_record/**/*_spec.rb'
+]
+
+(generic_specs + active_record_specs).each {|s| require s}
Something went wrong with that request. Please try again.