moxiesoft_parallel_tests 0.4.12
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/.gitignore +1 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +34 -0
- data/Rakefile +21 -0
- data/Readme.md +156 -0
- data/VERSION +1 -0
- data/bin/parallel_cucumber +2 -0
- data/bin/parallel_spec +2 -0
- data/bin/parallel_test +98 -0
- data/lib/parallel_cucumber.rb +29 -0
- data/lib/parallel_specs/spec_runtime_logger.rb +49 -0
- data/lib/parallel_specs.rb +48 -0
- data/lib/parallel_tests/grouper.rb +31 -0
- data/lib/parallel_tests/railtie.rb +10 -0
- data/lib/parallel_tests/tasks.rb +80 -0
- data/lib/parallel_tests.rb +122 -0
- data/lib/tasks/parallel_tests.rake +1 -0
- data/spec/integration_spec.rb +114 -0
- data/spec/parallel_cucumber_spec.rb +72 -0
- data/spec/parallel_specs_spec.rb +149 -0
- data/spec/parallel_tests_spec.rb +142 -0
- data/spec/spec_helper.rb +110 -0
- metadata +104 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.sh
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
diff-lcs (1.1.2)
|
5
|
+
gemcutter (0.6.1)
|
6
|
+
git (1.2.5)
|
7
|
+
jeweler (1.4.0)
|
8
|
+
gemcutter (>= 0.1.0)
|
9
|
+
git (>= 1.2.5)
|
10
|
+
rubyforge (>= 2.0.0)
|
11
|
+
json_pure (1.4.6)
|
12
|
+
parallel (0.5.1)
|
13
|
+
rake (0.8.7)
|
14
|
+
rspec (2.0.1)
|
15
|
+
rspec-core (~> 2.0.1)
|
16
|
+
rspec-expectations (~> 2.0.1)
|
17
|
+
rspec-mocks (~> 2.0.1)
|
18
|
+
rspec-core (2.0.1)
|
19
|
+
rspec-expectations (2.0.1)
|
20
|
+
diff-lcs (>= 1.1.2)
|
21
|
+
rspec-mocks (2.0.1)
|
22
|
+
rspec-core (~> 2.0.1)
|
23
|
+
rspec-expectations (~> 2.0.1)
|
24
|
+
rubyforge (2.0.4)
|
25
|
+
json_pure (>= 1.1.7)
|
26
|
+
|
27
|
+
PLATFORMS
|
28
|
+
ruby
|
29
|
+
|
30
|
+
DEPENDENCIES
|
31
|
+
jeweler
|
32
|
+
parallel
|
33
|
+
rake
|
34
|
+
rspec (~> 2)
|
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 = "moxiesoft_parallel_tests"
|
11
|
+
gem.summary = "Run tests / specs / features in parallel"
|
12
|
+
gem.email = "grosser.michael@gmail.com"
|
13
|
+
gem.homepage = "http://github.com/edebill-moxiesoft/#{gem.name}"
|
14
|
+
gem.authors = ['Michael Grosser', 'Eric DeBill']
|
15
|
+
gem.add_dependency "parallel"
|
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,156 @@
|
|
1
|
+
Speedup Test::Unit + RSpec + Cucumber by running parallel on multiple CPUs(or cores).
|
2
|
+
|
3
|
+
Setup for Rails
|
4
|
+
===============
|
5
|
+
|
6
|
+
## Install
|
7
|
+
### Rails 3
|
8
|
+
As gem
|
9
|
+
sudo gem install parallel_tests
|
10
|
+
# add to Gemfile
|
11
|
+
gem "parallel_tests", :group=>:development
|
12
|
+
|
13
|
+
OR as plugin
|
14
|
+
sudo gem install parallel
|
15
|
+
rails plugin install git://github.com/grosser/parallel_tests.git
|
16
|
+
|
17
|
+
### Rails 2
|
18
|
+
|
19
|
+
As gem
|
20
|
+
sudo gem install parallel_tests
|
21
|
+
# add to config/environments/development.rb
|
22
|
+
config.gem "parallel_tests"
|
23
|
+
# add to Rakefile
|
24
|
+
begin; require 'parallel_tests/tasks'; rescue LoadError; end
|
25
|
+
|
26
|
+
OR as plugin
|
27
|
+
|
28
|
+
sudo gem install parallel
|
29
|
+
./script/plugin install git://github.com/grosser/parallel_tests.git
|
30
|
+
|
31
|
+
## Setup
|
32
|
+
### 1: Add to `config/database.yml`
|
33
|
+
test:
|
34
|
+
database: xxx_test<%= ENV['TEST_ENV_NUMBER'] %>
|
35
|
+
|
36
|
+
### 2: Create additional database(s)
|
37
|
+
rake parallel:create
|
38
|
+
|
39
|
+
### 3: Copy development schema (repeat after migrations)
|
40
|
+
rake parallel:prepare
|
41
|
+
|
42
|
+
### 4: Run!
|
43
|
+
rake parallel:test # Test::Unit
|
44
|
+
rake parallel:spec # RSpec
|
45
|
+
rake parallel:features # Cucumber
|
46
|
+
|
47
|
+
rake parallel:test[1] --> force 1 CPU --> 86 seconds
|
48
|
+
rake parallel:test --> got 2 CPUs? --> 47 seconds
|
49
|
+
rake parallel:test --> got 4 CPUs? --> 26 seconds
|
50
|
+
...
|
51
|
+
|
52
|
+
Test just a subfolder (e.g. use one integration server per subfolder)
|
53
|
+
rake parallel:test[models]
|
54
|
+
rake parallel:test[something/else]
|
55
|
+
|
56
|
+
partial paths are OK too...
|
57
|
+
rake parallel:test[functional] == rake parallel:test[fun]
|
58
|
+
|
59
|
+
Example output
|
60
|
+
--------------
|
61
|
+
2 processes for 210 specs, ~ 105 specs per process
|
62
|
+
... test output ...
|
63
|
+
|
64
|
+
Results:
|
65
|
+
877 examples, 0 failures, 11 pending
|
66
|
+
843 examples, 0 failures, 1 pending
|
67
|
+
|
68
|
+
Took 29.925333 seconds
|
69
|
+
|
70
|
+
Even process runtimes (for specs only atm)
|
71
|
+
-----------------
|
72
|
+
Add to your `spec/parallel_spec.opts` (or `spec/spec.opts`) :
|
73
|
+
--format ParallelSpecs::SpecRuntimeLogger:tmp/parallel_profile.log
|
74
|
+
It will log test runtime and partition the test-load accordingly.
|
75
|
+
|
76
|
+
Setup for non-rails
|
77
|
+
===================
|
78
|
+
sudo gem install parallel_tests
|
79
|
+
# go to your project dir
|
80
|
+
parallel_test OR parallel_spec OR parallel_cucumber
|
81
|
+
# [Optional] use ENV['TEST_ENV_NUMBER'] inside your tests to select separate db/memcache/etc.
|
82
|
+
|
83
|
+
[optional] Only run selected files & folders:
|
84
|
+
parallel_test test/bar test/baz/xxx_text.rb
|
85
|
+
|
86
|
+
Options are:
|
87
|
+
-n [PROCESSES] How many processes to use, default: available CPUs
|
88
|
+
-p, --path [PATH] run tests inside this path only
|
89
|
+
--no-sort do not sort files before running them
|
90
|
+
-m, --multiply-processes [FLOAT] use given number as a multiplier of processes to run
|
91
|
+
-r, --root [PATH] execute test commands from this path
|
92
|
+
-e, --exec [COMMAND] execute this code parallel and with ENV['TEST_ENV_NUM']
|
93
|
+
-o, --test-options '[OPTIONS]' execute test commands with those options
|
94
|
+
-t, --type [TYPE] which type of tests to run? test, spec or features
|
95
|
+
--non-parallel execute same commands but do not in parallel, needs --exec
|
96
|
+
-v, --version Show Version
|
97
|
+
-h, --help Show this.
|
98
|
+
|
99
|
+
You can run any kind of code with -e / --execute
|
100
|
+
parallel_test -n 5 -e 'ruby -e "puts %[hello from process #{ENV[:TEST_ENV_NUMBER.to_s].inspect}]"'
|
101
|
+
hello from process "2"
|
102
|
+
hello from process ""
|
103
|
+
hello from process "3"
|
104
|
+
hello from process "5"
|
105
|
+
hello from process "4"
|
106
|
+
|
107
|
+
<table>
|
108
|
+
<tr><td></td><td>1 Process</td><td>2 Processes</td><td>4 Processes</td></tr>
|
109
|
+
<tr><td>RSpec spec-suite</td><td>18s</td><td>14s</td><td>10s</td></tr>
|
110
|
+
<tr><td>Rails-ActionPack</td><td>88s</td><td>53s</td><td>44s</td></tr>
|
111
|
+
</table>
|
112
|
+
|
113
|
+
TIPS
|
114
|
+
====
|
115
|
+
- [Capybara + Selenium] add to env.rb: `Capybara.server_port = 8888 + ENV['TEST_ENV_NUMBER'].to_i`
|
116
|
+
- [RSpec] add a `spec/parallel_spec.opts` to use different options, e.g. no --drb (default: `spec/spec.opts`)
|
117
|
+
- [RSpec] if something looks fishy try to delete `script/spec`
|
118
|
+
- [RSpec] if `script/spec` is missing parallel:spec uses just `spec` (which solves some issues with double-loaded environment.rb)
|
119
|
+
- [RSpec] 'script/spec_server' or [spork](http://github.com/timcharper/spork/tree/master) do not work in parallel
|
120
|
+
- [RSpec] `./script/generate rspec` if you are running rspec from gems (this plugin uses script/spec which may fail if rspec files are outdated)
|
121
|
+
- [Bundler] if you have a `Gemfile` then `bundle exec` will be used to run tests
|
122
|
+
- [Capybara setup](https://github.com/grosser/parallel_tests/wiki)
|
123
|
+
- [Sphinx setup](https://github.com/grosser/parallel_tests/wiki)
|
124
|
+
- [SQL schema format] use :ruby schema format to get faster parallel:prepare`
|
125
|
+
- with zsh this would be `rake "parallel:prepare[3]"`
|
126
|
+
|
127
|
+
TODO
|
128
|
+
====
|
129
|
+
- make jRuby compatible [basics](http://yehudakatz.com/2009/07/01/new-rails-isolation-testing/)
|
130
|
+
- make windows compatible
|
131
|
+
|
132
|
+
Authors
|
133
|
+
====
|
134
|
+
inspired by [pivotal labs](http://pivotallabs.com/users/miked/blog/articles/849-parallelize-your-rspec-suite)
|
135
|
+
|
136
|
+
### [Contributors](http://github.com/grosser/parallel_tests/contributors)
|
137
|
+
- [Charles Finkel](http://charlesfinkel.com/)
|
138
|
+
- [Indrek Juhkam](http://urgas.eu)
|
139
|
+
- [Jason Morrison](http://jayunit.net)
|
140
|
+
- [jinzhu](http://github.com/jinzhu)
|
141
|
+
- [Joakim Kolsjö](http://www.rubyblocks.se)
|
142
|
+
- [Kevin Scaldeferri](http://kevin.scaldeferri.com/blog/)
|
143
|
+
- [Kpumuk](http://kpumuk.info/)
|
144
|
+
- [Maksim Horbul](http://github.com/mhorbul)
|
145
|
+
- [Pivotal Labs](http://www.pivotallabs.com)
|
146
|
+
- [Rohan Deshpande](http://github.com/rdeshpande)
|
147
|
+
- [Tchandy](http://thiagopradi.net/)
|
148
|
+
- [Terence Lee](http://hone.heroku.com/)
|
149
|
+
- [Will Bryant](http://willbryant.net/)
|
150
|
+
- [Fred Wu](http://fredwu.me)
|
151
|
+
- [xxx](https://github.com/xxx)
|
152
|
+
- [Levent Ali](http://purebreeze.com/)
|
153
|
+
|
154
|
+
[Michael Grosser](http://grosser.it)<br/>
|
155
|
+
michael@grosser.it<br/>
|
156
|
+
Hereby placed under public domain, do what you want, just do not hold me accountable...
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.4.12
|
data/bin/parallel_spec
ADDED
data/bin/parallel_test
ADDED
@@ -0,0 +1,98 @@
|
|
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", '--path [PATH]', "run tests inside this path only"){|path| options[:path_prefix] = path }
|
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("-e", '--exec [COMMAND]', "execute this code parallel and with ENV['TEST_ENV_NUM']"){|path| options[:execute] = path }
|
25
|
+
opts.on("-o", "--test-options '[OPTIONS]'", "execute test commands with those options"){|arg| options[:test_options] = arg }
|
26
|
+
opts.on("-t", "--type [TYPE]", "which type of tests to run? test, spec or features"){|type| options[:type] = type }
|
27
|
+
opts.on("--non-parallel", "execute same commands but do not in parallel, needs --exec"){|type| options[:non_parallel] = true }
|
28
|
+
opts.on('-v', '--version', 'Show Version'){ puts ParallelTests::VERSION; exit}
|
29
|
+
opts.on("-h", "--help", "Show this.") { puts opts; exit }
|
30
|
+
end.parse!
|
31
|
+
|
32
|
+
# get files to run from arguments
|
33
|
+
options[:files] = ARGV if ARGV.size > 0
|
34
|
+
|
35
|
+
num_processes = options[:count] || Parallel.processor_count
|
36
|
+
num_processes = num_processes * (options[:multiply] || 1)
|
37
|
+
|
38
|
+
if options[:execute]
|
39
|
+
runs = (0...num_processes).to_a
|
40
|
+
results = if options[:non_parallel]
|
41
|
+
runs.map do |i|
|
42
|
+
ParallelTests.execute_command(options[:execute], i)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
Parallel.map(runs, :in_processes => num_processes) do |i|
|
46
|
+
ParallelTests.execute_command(options[:execute], i)
|
47
|
+
end
|
48
|
+
end.flatten
|
49
|
+
abort if results.any?{|r| r[:exit_status] != 0 }
|
50
|
+
else
|
51
|
+
lib, name, task = {
|
52
|
+
'test' => ["tests", "test", "test"],
|
53
|
+
'spec' => ["specs", "spec", "spec"],
|
54
|
+
'features' => ["cucumber", "feature", "features"]
|
55
|
+
}[options[:type]||'test']
|
56
|
+
|
57
|
+
require "parallel_#{lib}"
|
58
|
+
klass = eval("Parallel#{lib.capitalize}")
|
59
|
+
|
60
|
+
start = Time.now
|
61
|
+
|
62
|
+
options[:path_prefix] = options[:path_prefix] ? options[:path_prefix].split(',') : ['']
|
63
|
+
tests_folder = options[:path_prefix].collect do |path|
|
64
|
+
tests_folder = File.join(task, path.to_s)
|
65
|
+
tests_folder = File.join(options[:root], tests_folder) unless options[:root].to_s.empty?
|
66
|
+
tests_folder
|
67
|
+
end
|
68
|
+
tests_folder = nil if tests_folder.empty?
|
69
|
+
|
70
|
+
puts "test folders => #{tests_folder}" if tests_folder
|
71
|
+
|
72
|
+
groups = klass.tests_in_groups(options[:files] || tests_folder, num_processes, :no_sort => options[:no_sort])
|
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[:test_options])
|
83
|
+
end
|
84
|
+
|
85
|
+
#parse and print results
|
86
|
+
results = klass.find_results(test_results.map{|result| result[:stdout] }*"")
|
87
|
+
puts ""
|
88
|
+
puts "Results:"
|
89
|
+
results.each{|r| puts r }
|
90
|
+
|
91
|
+
#report total time taken
|
92
|
+
puts ""
|
93
|
+
puts "Took #{Time.now - start} seconds"
|
94
|
+
|
95
|
+
#exit with correct status code so rake parallel:test && echo 123 works
|
96
|
+
failed = test_results.any?{|result| result[:exit_status] != 0 }
|
97
|
+
abort "#{name.capitalize}s Failed" if failed
|
98
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'parallel_tests')
|
2
|
+
|
3
|
+
class ParallelCucumber < ParallelTests
|
4
|
+
def self.run_tests(test_files, process_number, options)
|
5
|
+
color = ($stdout.tty? ? 'AUTOTEST=1 ; export AUTOTEST ;' : '')#display color when we are in a terminal
|
6
|
+
cmd = "#{color} #{executable} #{options} #{test_files*' '}"
|
7
|
+
execute_command(cmd, process_number)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.executable
|
11
|
+
if bundler_enabled?
|
12
|
+
"bundle exec cucumber"
|
13
|
+
elsif File.file?("script/cucumber")
|
14
|
+
"script/cucumber"
|
15
|
+
else
|
16
|
+
"cucumber"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def self.test_suffix
|
23
|
+
".feature"
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.line_is_result?(line)
|
27
|
+
line =~ /^\d+ (steps|scenarios)/
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec/runner/formatter/progress_bar_formatter'
|
2
|
+
|
3
|
+
class ParallelSpecs::SpecRuntimeLogger < Spec::Runner::Formatter::BaseTextFormatter
|
4
|
+
def initialize(options, output)
|
5
|
+
if String === output
|
6
|
+
FileUtils.mkdir_p(File.dirname(output))
|
7
|
+
File.open(output,'w'){|f| f.write ''} # clean the file
|
8
|
+
@output = File.open(output, 'a+') #append so that multiple processes can write at once
|
9
|
+
else
|
10
|
+
@output = output
|
11
|
+
end
|
12
|
+
@example_times = Hash.new(0)
|
13
|
+
end
|
14
|
+
|
15
|
+
def example_started(*args)
|
16
|
+
@time = Time.now
|
17
|
+
end
|
18
|
+
|
19
|
+
def example_passed(example)
|
20
|
+
file = example.location.split(':').first
|
21
|
+
@example_times[file] += Time.now - @time
|
22
|
+
end
|
23
|
+
|
24
|
+
def start_dump(*args)
|
25
|
+
return unless ENV['TEST_ENV_NUMBER'] #only record when running in parallel
|
26
|
+
# TODO: Figure out why sometimes time can be less than 0
|
27
|
+
@output.puts @example_times.map { |file, time| "#{file}:#{time > 0 ? time : 0}" }
|
28
|
+
@output.flush
|
29
|
+
end
|
30
|
+
|
31
|
+
# stubs so that rspec doe not crash
|
32
|
+
|
33
|
+
def example_pending(*args)
|
34
|
+
end
|
35
|
+
|
36
|
+
def dump_summary(*args)
|
37
|
+
end
|
38
|
+
|
39
|
+
def dump_pending(*args)
|
40
|
+
end
|
41
|
+
|
42
|
+
def dump_failure(*args)
|
43
|
+
end
|
44
|
+
|
45
|
+
#stolen from Rspec
|
46
|
+
def close
|
47
|
+
@output.close if (IO === @output) & (@output != $stdout)
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'parallel_tests')
|
2
|
+
|
3
|
+
class ParallelSpecs < ParallelTests
|
4
|
+
def self.run_tests(test_files, process_number, options)
|
5
|
+
exe = executable # its expensive with bundler, so do not call it twice
|
6
|
+
cmd = "#{color} #{exe} #{options} #{spec_opts(exe)} #{test_files*' '}"
|
7
|
+
execute_command(cmd, process_number)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.executable
|
11
|
+
cmd = if File.file?("script/spec")
|
12
|
+
"script/spec"
|
13
|
+
elsif bundler_enabled?
|
14
|
+
cmd = (run("bundle show rspec") =~ %r{/rspec-1[^/]+$} ? "spec" : "rspec")
|
15
|
+
"bundle exec #{cmd}"
|
16
|
+
else
|
17
|
+
%w[spec rspec].detect{|cmd| system "#{cmd} --version > /dev/null 2>&1" }
|
18
|
+
end
|
19
|
+
cmd or raise("Can't find executables rspec or spec")
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
# so it can be stubbed....
|
25
|
+
def self.run(cmd)
|
26
|
+
`#{cmd}`
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.spec_opts(executable)
|
30
|
+
opts = ['spec/parallel_spec.opts', 'spec/spec.opts'].detect{|f| File.file?(f) }
|
31
|
+
return unless opts
|
32
|
+
if executable =~ /\brspec\b/
|
33
|
+
# RSpec2 does not handle -O, so we inline the options
|
34
|
+
File.read(opts).tr("\n", ' ')
|
35
|
+
else
|
36
|
+
"-O #{opts}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
#display color when we are in a terminal
|
41
|
+
def self.color
|
42
|
+
($stdout.tty? ? 'RSPEC_COLOR=1 ; export RSPEC_COLOR ;' : '')
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.test_suffix
|
46
|
+
"_spec.rb"
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class ParallelTests
|
2
|
+
class Grouper
|
3
|
+
def self.in_groups(items, num_groups)
|
4
|
+
[].tap do |groups|
|
5
|
+
while ! items.empty?
|
6
|
+
(0...num_groups).map do |group_number|
|
7
|
+
groups[group_number] ||= []
|
8
|
+
groups[group_number] << items.shift
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.in_even_groups_by_size(items_with_sizes, num_groups)
|
15
|
+
items_with_size = smallest_first(items_with_sizes)
|
16
|
+
groups = Array.new(num_groups){{:items => [], :size => 0}}
|
17
|
+
items_with_size.each do |item, size|
|
18
|
+
# always add to smallest group
|
19
|
+
smallest = groups.sort_by{|g| g[:size] }.first
|
20
|
+
smallest[:items] << item
|
21
|
+
smallest[:size] += size
|
22
|
+
end
|
23
|
+
|
24
|
+
groups.map{|g| g[:items] }
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.smallest_first(files)
|
28
|
+
files.sort_by{|item, size| size }.reverse
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
namespace :parallel do
|
2
|
+
def run_in_parallel(cmd, options)
|
3
|
+
count = (options[:count] ? options[:count].to_i : nil)
|
4
|
+
executable = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'parallel_test')
|
5
|
+
command = "#{executable} --exec '#{cmd}' -n #{count} #{'--non-parallel' if options[:non_parallel]}"
|
6
|
+
abort unless system(command)
|
7
|
+
end
|
8
|
+
|
9
|
+
desc "create test databases via db:create --> parallel:create[num_cpus]"
|
10
|
+
task :create, :count do |t,args|
|
11
|
+
run_in_parallel('rake db:create RAILS_ENV=test', args)
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "drop test databases via db:drop --> parallel:drop[num_cpus]"
|
15
|
+
task :drop, :count do |t,args|
|
16
|
+
run_in_parallel('rake db:drop RAILS_ENV=test', args)
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "update test databases by dumping and loading --> parallel:prepare[num_cpus]"
|
20
|
+
task(:prepare, [:count] => 'db:abort_if_pending_migrations') do |t,args|
|
21
|
+
if ActiveRecord::Base.schema_format == :ruby
|
22
|
+
# dump then load in parallel
|
23
|
+
Rake::Task['db:schema:dump'].invoke
|
24
|
+
Rake::Task['parallel:load_schema'].invoke(args[:count])
|
25
|
+
else
|
26
|
+
# there is no separate dump / load for schema_format :sql -> do it safe and slow
|
27
|
+
args = args.to_hash.merge(:non_parallel => true) # normal merge returns nil
|
28
|
+
run_in_parallel('rake db:test:prepare --trace', args)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# when dumping/resetting takes too long
|
33
|
+
desc "update test databases via db:mgrate --> parallel:migrate[num_cpus]"
|
34
|
+
task :migrate, :count do |t,args|
|
35
|
+
run_in_parallel('rake db:migrate RAILS_ENV=test', args)
|
36
|
+
end
|
37
|
+
|
38
|
+
# just load the schema (good for integration server <-> no development db)
|
39
|
+
desc "load dumped schema for test databases via db:schema:load --> parallel:load_schema[num_cpus]"
|
40
|
+
task :load_schema, :count do |t,args|
|
41
|
+
run_in_parallel('rake db:test:load', args)
|
42
|
+
end
|
43
|
+
|
44
|
+
['test', 'spec', 'features'].each do |type|
|
45
|
+
desc "run #{type} in parallel with parallel:#{type}[num_cpus]"
|
46
|
+
task type, :count, :path_prefix, :options do |t,args|
|
47
|
+
$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
48
|
+
require "parallel_tests"
|
49
|
+
count, prefix, options = ParallelTests.parse_rake_args(args)
|
50
|
+
executable = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'parallel_test')
|
51
|
+
command = "#{executable} --type #{type} -n #{count} -p '#{prefix}' -r '#{Rails.root}' -o '#{options}'"
|
52
|
+
abort unless system(command) # allow to chain tasks e.g. rake parallel:spec parallel:features
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
#backwards compatability
|
58
|
+
#spec:parallel:prepare
|
59
|
+
#spec:parallel
|
60
|
+
#test:parallel
|
61
|
+
namespace :spec do
|
62
|
+
namespace :parallel do
|
63
|
+
task :prepare, :count do |t,args|
|
64
|
+
$stderr.puts "WARNING -- Deprecated! use parallel:prepare"
|
65
|
+
Rake::Task['parallel:prepare'].invoke(args[:count])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
task :parallel, :count, :path_prefix do |t,args|
|
70
|
+
$stderr.puts "WARNING -- Deprecated! use parallel:spec"
|
71
|
+
Rake::Task['parallel:spec'].invoke(args[:count], args[:path_prefix])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
namespace :test do
|
76
|
+
task :parallel, :count, :path_prefix do |t,args|
|
77
|
+
$stderr.puts "WARNING -- Deprecated! use parallel:test"
|
78
|
+
Rake::Task['parallel:test'].invoke(args[:count], args[:path_prefix])
|
79
|
+
end
|
80
|
+
end
|