parallelized_specs 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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