parallelized_specs 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source :rubygems
2
+
3
+ gem 'parallel'
4
+
5
+ group :dev do
6
+ gem 'test-unit', :platform => :ruby_19
7
+ gem 'rspec', '>=2.4'
8
+ gem 'rake'
9
+ gem 'jeweler'
10
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,30 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.2)
5
+ git (1.2.5)
6
+ jeweler (1.6.3)
7
+ bundler (~> 1.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ parallel (0.5.1)
11
+ rake (0.8.7)
12
+ rspec (2.6.0)
13
+ rspec-core (~> 2.6.0)
14
+ rspec-expectations (~> 2.6.0)
15
+ rspec-mocks (~> 2.6.0)
16
+ rspec-core (2.6.4)
17
+ rspec-expectations (2.6.0)
18
+ diff-lcs (~> 1.1.2)
19
+ rspec-mocks (2.6.0)
20
+ test-unit (2.4.4)
21
+
22
+ PLATFORMS
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ jeweler
27
+ parallel
28
+ rake
29
+ rspec (>= 2.4)
30
+ test-unit
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ task :default => :spec
2
+ require "rspec/core/rake_task"
3
+ RSpec::Core::RakeTask.new(:spec) do |t|
4
+ t.rspec_opts = '--backtrace --color'
5
+ end
6
+
7
+ begin
8
+ require 'jeweler'
9
+ Jeweler::Tasks.new do |gem|
10
+ gem.name = "parallelized_specs"
11
+ gem.summary = "Run rspec tests in parallel"
12
+ gem.email = "jake@instructure.com"
13
+ gem.homepage = "http://github.com/jake/#{gem.name}"
14
+ gem.authors = "Jake Sorce, Bryan Madsen"
15
+ gem.version = "0.0.1"
16
+ end
17
+
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"
21
+ end
data/Readme.md ADDED
@@ -0,0 +1,240 @@
1
+ Speedup Test::RSpec by running parallel on multiple CPUs (or cores).<br/>
2
+ ParallelizedSpecs splits tests into even groups(by number of tests or runtime) and runs each group in a single process with its own database.
3
+
4
+ Setup for Rails
5
+ ===============
6
+ ## Install
7
+ ### Rails 3
8
+ If you use RSpec: ensure you got >= 2.4
9
+
10
+ As gem
11
+
12
+ # add to Gemfile
13
+ gem "parallel_tests", :group => :development
14
+
15
+ OR as plugin
16
+
17
+ rails plugin install git://github.com/grosser/parallel_tests.git
18
+
19
+ # add to Gemfile
20
+ gem "parallel", :group => :development
21
+
22
+
23
+ ### Rails 2
24
+
25
+ As gem
26
+
27
+ gem install parallel_tests
28
+
29
+ # add to config/environments/development.rb
30
+ config.gem "parallel_tests"
31
+
32
+ # add to Rakefile
33
+ begin; require 'parallel_tests/tasks'; rescue LoadError; end
34
+
35
+ OR as plugin
36
+
37
+ gem install parallel
38
+
39
+ # add to config/environments/development.rb
40
+ config.gem "parallel"
41
+
42
+ ./script/plugin install git://github.com/grosser/parallel_tests.git
43
+
44
+ ## Setup
45
+ ParallelTests uses 1 database per test-process, 2 processes will use `*_test` and `*_test2`.
46
+
47
+
48
+ ### 1: Add to `config/database.yml`
49
+ test:
50
+ database: xxx_test<%= ENV['TEST_ENV_NUMBER'] %>
51
+
52
+ ### 2: Create additional database(s)
53
+ rake parallel:create
54
+
55
+ ### 3: Copy development schema (repeat after migrations)
56
+ rake parallel:prepare
57
+
58
+ ### 4: Run!
59
+ rake parallel:test # Test::Unit
60
+ rake parallel:spec # RSpec
61
+ rake parallel:features # Cucumber
62
+
63
+ rake parallel:test[1] --> force 1 CPU --> 86 seconds
64
+ rake parallel:test --> got 2 CPUs? --> 47 seconds
65
+ rake parallel:test --> got 4 CPUs? --> 26 seconds
66
+ ...
67
+
68
+ Test by pattern (e.g. use one integration server per subfolder / see if you broke any 'user'-related tests)
69
+
70
+ rake parallel:test[^unit] # everything in test/unit folder (every test file matching /^unit/)
71
+ rake parallel:test[user] # run users_controller + user_helper + user tests
72
+ rake parallel:test['user|product'] # run user and product related tests
73
+
74
+
75
+ Example output
76
+ --------------
77
+ 2 processes for 210 specs, ~ 105 specs per process
78
+ ... test output ...
79
+
80
+ 843 examples, 0 failures, 1 pending
81
+
82
+ Took 29.925333 seconds
83
+
84
+ Loggers
85
+ ===================
86
+
87
+ Even process runtimes
88
+ -----------------
89
+
90
+ Log test runtime to give each process the same runtime.
91
+
92
+ Rspec: Add to your `spec/parallel_spec.opts` (or `spec/spec.opts`) :
93
+
94
+ RSpec 1.x:
95
+ --format progress
96
+ --require parallel_specs/spec_runtime_logger
97
+ --format ParallelSpecs::SpecRuntimeLogger:tmp/parallel_profile.log
98
+ RSpec >= 2.4:
99
+ If installed as plugin: -I vendor/plugins/parallel_tests/lib
100
+ --format progress
101
+ --format ParallelSpecs::SpecRuntimeLogger --out tmp/parallel_profile.log
102
+
103
+ Test::Unit: Add to your `test_helper.rb`:
104
+ require 'parallel_tests/runtime_logger'
105
+
106
+
107
+ SpecSummaryLogger
108
+ --------------------
109
+
110
+ This logger logs the test output without the different processes overwriting each other.
111
+
112
+ Add the following to your `spec/parallel_spec.opts` (or `spec/spec.opts`) :
113
+
114
+ RSpec 1.x:
115
+ --format progress
116
+ --require parallel_specs/spec_summary_logger
117
+ --format ParallelSpecs::SpecSummaryLogger:tmp/spec_summary.log
118
+ RSpec >= 2.2:
119
+ If installed as plugin: -I vendor/plugins/parallel_tests/lib
120
+ --format progress
121
+ --format ParallelSpecs::SpecSummaryLogger --out tmp/spec_summary.log
122
+
123
+ SpecFailuresLogger
124
+ -----------------------
125
+
126
+ This logger produces pasteable command-line snippets for each failed example.
127
+
128
+ E.g.
129
+
130
+ rspec /path/to/my_spec.rb:123 # should do something
131
+
132
+ Add the following to your `spec/parallel_spec.opts` (or `spec/spec.opts`) :
133
+
134
+ RSpec 1.x:
135
+ --format progress
136
+ --require parallel_specs/spec_failures_logger
137
+ --format ParallelSpecs::SpecFailuresLogger:tmp/failing_specs.log
138
+ RSpec >= 2.4:
139
+ If installed as plugin: -I vendor/plugins/parallel_tests/lib
140
+ --format progress
141
+ --format ParallelSpecs::SpecFailuresLogger --out tmp/failing_specs.log
142
+
143
+ Setup for non-rails
144
+ ===================
145
+ sudo gem install parallel_tests
146
+ # go to your project dir
147
+ parallel_test OR parallel_spec OR parallel_cucumber
148
+ # [Optional] use ENV['TEST_ENV_NUMBER'] inside your tests to select separate db/memcache/etc.
149
+
150
+ [optional] Only run selected files & folders:
151
+
152
+ parallel_test test/bar test/baz/xxx_text.rb
153
+
154
+ Options are:
155
+
156
+ -n [PROCESSES] How many processes to use, default: available CPUs
157
+ -p, --path [PATH] run tests inside this path only
158
+ --no-sort do not sort files before running them
159
+ -m, --multiply-processes [FLOAT] use given number as a multiplier of processes to run
160
+ -r, --root [PATH] execute test commands from this path
161
+ -e, --exec [COMMAND] execute this code parallel and with ENV['TEST_ENV_NUM']
162
+ -o, --test-options '[OPTIONS]' execute test commands with those options
163
+ -t, --type [TYPE] which type of tests to run? test, spec or features
164
+ --non-parallel execute same commands but do not in parallel, needs --exec
165
+ -v, --version Show Version
166
+ -h, --help Show this.
167
+
168
+ You can run any kind of code with -e / --execute
169
+
170
+ parallel_test -n 5 -e 'ruby -e "puts %[hello from process #{ENV[:TEST_ENV_NUMBER.to_s].inspect}]"'
171
+ hello from process "2"
172
+ hello from process ""
173
+ hello from process "3"
174
+ hello from process "5"
175
+ hello from process "4"
176
+
177
+ <table>
178
+ <tr><td></td><td>1 Process</td><td>2 Processes</td><td>4 Processes</td></tr>
179
+ <tr><td>RSpec spec-suite</td><td>18s</td><td>14s</td><td>10s</td></tr>
180
+ <tr><td>Rails-ActionPack</td><td>88s</td><td>53s</td><td>44s</td></tr>
181
+ </table>
182
+
183
+ TIPS
184
+ ====
185
+ - [Capybara + Selenium] add to env.rb: `Capybara.server_port = 8888 + ENV['TEST_ENV_NUMBER'].to_i`
186
+ - [RSpec] add a `spec/parallel_spec.opts` to use different options, e.g. no --drb (default: `spec/spec.opts`)
187
+ - [RSpec] if something looks fishy try to delete `script/spec`
188
+ - [RSpec] if `script/spec` is missing parallel:spec uses just `spec` (which solves some issues with double-loaded environment.rb)
189
+ - [RSpec] 'script/spec_server' or [spork](http://github.com/timcharper/spork/tree/master) do not work in parallel
190
+ - [RSpec] `./script/generate rspec` if you are running rspec from gems (this plugin uses script/spec which may fail if rspec files are outdated)
191
+ - [RSpec] remove --loadby from you spec/*.opts
192
+ - [Bundler] if you have a `Gemfile` then `bundle exec` will be used to run tests
193
+ - [Capybara setup](https://github.com/grosser/parallel_tests/wiki)
194
+ - [Sphinx setup](https://github.com/grosser/parallel_tests/wiki)
195
+ - [Capistrano setup](https://github.com/grosser/parallel_tests/wiki/Remotely-with-capistrano) let your tests run on a big box instead of your laptop
196
+ - [SQL schema format] use :ruby schema format to get faster parallel:prepare`
197
+ - [ActiveRecord] if you do not have `db:abort_if_pending_migrations` add this to your Rakefile: `task('db:abort_if_pending_migrations'){}`
198
+ - `export PARALLEL_TEST_PROCESSORS=X` in your environment and parallel_tests will use this number of processors by default
199
+ - with zsh this would be `rake "parallel:prepare[3]"`
200
+
201
+ TODO
202
+ ====
203
+ - make jRuby compatible [basics](http://yehudakatz.com/2009/07/01/new-rails-isolation-testing/)
204
+ - make windows compatible
205
+
206
+ Authors
207
+ ====
208
+ inspired by [pivotal labs](http://pivotallabs.com/users/miked/blog/articles/849-parallelize-your-rspec-suite)
209
+
210
+ ### [Contributors](http://github.com/grosser/parallel_tests/contributors)
211
+ - [Charles Finkel](http://charlesfinkel.com/)
212
+ - [Indrek Juhkam](http://urgas.eu)
213
+ - [Jason Morrison](http://jayunit.net)
214
+ - [jinzhu](http://github.com/jinzhu)
215
+ - [Joakim Kolsjö](http://www.rubyblocks.se)
216
+ - [Kevin Scaldeferri](http://kevin.scaldeferri.com/blog/)
217
+ - [Kpumuk](http://kpumuk.info/)
218
+ - [Maksim Horbul](http://github.com/mhorbul)
219
+ - [Pivotal Labs](http://www.pivotallabs.com)
220
+ - [Rohan Deshpande](http://github.com/rdeshpande)
221
+ - [Tchandy](http://thiagopradi.net/)
222
+ - [Terence Lee](http://hone.heroku.com/)
223
+ - [Will Bryant](http://willbryant.net/)
224
+ - [Fred Wu](http://fredwu.me)
225
+ - [xxx](https://github.com/xxx)
226
+ - [Levent Ali](http://purebreeze.com/)
227
+ - [Michael Kintzer](https://github.com/rockrep)
228
+ - [nathansobo](https://github.com/nathansobo)
229
+ - [Joe Yates](http://titusd.co.uk)
230
+ - [asmega](http://www.ph-lee.com)
231
+ - [Doug Barth](https://github.com/dougbarth)
232
+ - [Geoffrey Hichborn](https://github.com/phene)
233
+ - [Trae Robrock](https://github.com/trobrock)
234
+ - [Lawrence Wang](https://github.com/levity)
235
+ - [Sean Walbran](https://github.com/seanwalbran)
236
+
237
+ [Michael Grosser](http://grosser.it)<br/>
238
+ michael@grosser.it<br/>
239
+ Hereby placed under public domain, do what you want, just do not hold me accountable...<br/>
240
+ [![Flattr](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=grosser&url=https://github.com/grosser/parallel_tests&title=parallel_tests&language=en_GB&tags=github&category=software)
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/bin/parallel_spec ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ exec "#{File.join(File.dirname(__FILE__), 'parallel_test')} -t spec #{ARGV * ' '}"
data/bin/parallel_test ADDED
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'optparse'
4
+ require 'parallel'
5
+ raise "please ' gem install parallel '" if Gem::Version.new(Parallel::VERSION) < Gem::Version.new('0.4.2')
6
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
7
+ require "parallel_tests"
8
+
9
+ options = {}
10
+ OptionParser.new do |opts|
11
+ opts.banner = <<BANNER
12
+ Run all tests in parallel, giving each process ENV['TEST_ENV_NUMBER'] ('', '2', '3', ...)
13
+
14
+ [optional] Only run selected files & folders:
15
+ parallel_test test/bar test/baz/xxx_text.rb
16
+
17
+ Options are:
18
+ BANNER
19
+ opts.on("-n [PROCESSES]", Integer, "How many processes to use, default: available CPUs"){|n| options[:count] = n }
20
+ opts.on("-p", '--pattern [PATTERN]', "run tests matching this pattern"){|pattern| options[:pattern] = pattern }
21
+ opts.on("--no-sort", "do not sort files before running them"){ |no_sort| options[:no_sort] = no_sort }
22
+ opts.on("-m [FLOAT]", "--multiply-processes [FLOAT]", Float, "use given number as a multiplier of processes to run"){ |multiply| options[:multiply] = multiply }
23
+ opts.on("-r", '--root [PATH]', "execute test commands from this path"){|path| options[:root] = path }
24
+ opts.on("-s [PATTERN]", "--single [PATTERN]", "Run all matching files in only one process") do |pattern|
25
+ options[:single_process] ||= []
26
+ options[:single_process] << /#{pattern}/
27
+ end
28
+ opts.on("-e", '--exec [COMMAND]', "execute this code parallel and with ENV['TEST_ENV_NUM']"){|path| options[:execute] = path }
29
+ opts.on("-o", "--test-options '[OPTIONS]'", "execute test commands with those options"){|arg| options[:test_options] = arg }
30
+ opts.on("-t", "--type [TYPE]", "which type of tests to run? test, spec or features"){|type| options[:type] = type }
31
+ opts.on("--non-parallel", "execute same commands but do not in parallel, needs --exec"){ options[:non_parallel] = true }
32
+ opts.on("--chunk-timeout [TIMEOUT]", "timeout before re-printing the output of a child-process"){|timeout| options[:chunk_timeout] = timeout.to_f }
33
+ opts.on('-v', '--version', 'Show Version'){ puts ParallelTests::VERSION; exit}
34
+ opts.on("-h", "--help", "Show this.") { puts opts; exit }
35
+ end.parse!
36
+
37
+ raise "--no-sort and --single-process are not supported" if options[:no_sort] and options[:single_process]
38
+
39
+ # get files to run from arguments
40
+ options[:files] = ARGV if ARGV.size > 0
41
+
42
+ num_processes = options[:count] || Parallel.processor_count
43
+ num_processes = num_processes * (options[:multiply] || 1)
44
+
45
+ if options[:execute]
46
+ runs = (0...num_processes).to_a
47
+ results = if options[:non_parallel]
48
+ runs.map do |i|
49
+ ParallelTests.execute_command(options[:execute], i, options)
50
+ end
51
+ else
52
+ Parallel.map(runs, :in_processes => num_processes) do |i|
53
+ ParallelTests.execute_command(options[:execute], i, options)
54
+ end
55
+ end.flatten
56
+ abort if results.any?{|r| r[:exit_status] != 0 }
57
+ else
58
+ lib, name, task = {
59
+ 'test' => ["tests", "test", "test"],
60
+ 'spec' => ["specs", "spec", "spec"],
61
+ 'features' => ["cucumber", "feature", "features"]
62
+ }[options[:type]||'test']
63
+
64
+ require "parallel_#{lib}"
65
+ klass = eval("Parallel#{lib.capitalize}")
66
+
67
+ start = Time.now
68
+
69
+ tests_folder = task
70
+ tests_folder = File.join(options[:root], tests_folder) unless options[:root].to_s.empty?
71
+
72
+ groups = klass.tests_in_groups(options[:files] || tests_folder, num_processes, options)
73
+ num_processes = groups.size
74
+
75
+ #adjust processes to groups
76
+ abort "no #{name}s found!" if groups.size == 0
77
+
78
+ num_tests = groups.inject(0){|sum,item| sum + item.size }
79
+ puts "#{num_processes} processes for #{num_tests} #{name}s, ~ #{num_tests / groups.size} #{name}s per process"
80
+
81
+ test_results = Parallel.map(groups, :in_processes => num_processes) do |group|
82
+ klass.run_tests(group, groups.index(group), options)
83
+ end
84
+
85
+ #parse and print results
86
+ results = klass.find_results(test_results.map{|result| result[:stdout] }*"")
87
+ puts ""
88
+ puts klass.summarize_results(results)
89
+
90
+ #report total time taken
91
+ puts ""
92
+ puts "Took #{Time.now - start} seconds"
93
+
94
+ #exit with correct status code so rake parallel:test && echo 123 works
95
+ failed = test_results.any?{|result| result[:exit_status] != 0 }
96
+ abort "#{name.capitalize}s Failed" if failed
97
+ end
@@ -0,0 +1,30 @@
1
+ require 'parallel_specs'
2
+ require File.join(File.dirname(__FILE__), 'spec_logger_base')
3
+
4
+ class ParallelSpecs::SpecErrorCountLogger < ParallelSpecs::SpecLoggerBase
5
+ def initialize(options, output=nil)
6
+ super
7
+ @passed_examples = []
8
+ @pending_examples = []
9
+ @failed_examples = []
10
+ end
11
+
12
+ def example_passed(example)
13
+ @passed_examples << example
14
+ end
15
+
16
+ def example_pending(*args)
17
+ @pending_examples << args
18
+ end
19
+
20
+ def example_failed(example, count, failure)
21
+ @failed_examples << failure
22
+ end
23
+
24
+ def dump_summary(duration, example_count, failure_count, pending_count)
25
+ lock_output do
26
+ @output.puts "#{ @failed_examples.size }"
27
+ end
28
+ @output.flush
29
+ end
30
+ end
@@ -0,0 +1,45 @@
1
+ require 'parallel_specs'
2
+ require File.join(File.dirname(__FILE__), 'spec_logger_base')
3
+
4
+ class ParallelSpecs::SpecErrorLogger < ParallelSpecs::SpecLoggerBase
5
+ def initialize(options, output=nil)
6
+ super
7
+ @passed_examples = []
8
+ @pending_examples = []
9
+ @failed_examples = []
10
+ end
11
+
12
+ def example_passed(example)
13
+ @passed_examples << example
14
+ end
15
+
16
+ def example_pending(*args)
17
+ @pending_examples << args
18
+ end
19
+
20
+ def example_failed(example, count, failure)
21
+ @failed_examples << failure
22
+ end
23
+
24
+ def dump_summary(duration, example_count, failure_count, pending_count)
25
+ lock_output do
26
+ env_test_number = ENV['TEST_ENV_NUMBER']
27
+ env_test_number = 1 if ENV['TEST_ENV_NUMBER'].blank?
28
+ @output.puts ""
29
+ @output.puts ""
30
+ @output.puts "FOR TEST EXECUTOR #{env_test_number}: #{@failed_examples.size} failed, #{@passed_examples.size} passed:"
31
+ @failed_examples.each.with_index do | failure, i |
32
+ @output.puts ""
33
+ @output.puts "#{ i + 1 })"
34
+ @output.puts failure.header
35
+ unless failure.exception.nil?
36
+ @output.puts failure.exception.to_s
37
+ failure.exception.backtrace.each do | caller |
38
+ @output.puts caller
39
+ end
40
+ end
41
+ end
42
+ end
43
+ @output.flush
44
+ end
45
+ end