moxiesoft_parallel_tests 0.4.13 → 0.6.18

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -3,7 +3,9 @@ source :rubygems
3
3
  gem 'parallel'
4
4
 
5
5
  group :dev do
6
+ gem 'test-unit', :platform => :ruby_19
7
+ gem 'rspec', '>=2.4'
8
+ gem 'cucumber'
6
9
  gem 'rake'
7
- gem 'rspec', '~>2'
8
10
  gem 'jeweler'
9
- end
11
+ end
data/Gemfile.lock CHANGED
@@ -1,34 +1,42 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
+ builder (3.0.0)
5
+ cucumber (1.1.4)
6
+ builder (>= 2.1.2)
7
+ diff-lcs (>= 1.1.2)
8
+ gherkin (~> 2.7.1)
9
+ json (>= 1.4.6)
10
+ term-ansicolor (>= 1.0.6)
4
11
  diff-lcs (1.1.2)
5
- gemcutter (0.6.1)
12
+ gherkin (2.7.6)
13
+ json (>= 1.4.6)
6
14
  git (1.2.5)
7
- jeweler (1.4.0)
8
- gemcutter (>= 0.1.0)
15
+ jeweler (1.6.3)
16
+ bundler (~> 1.0)
9
17
  git (>= 1.2.5)
10
- rubyforge (>= 2.0.0)
11
- json_pure (1.4.6)
18
+ rake
19
+ json (1.6.4)
12
20
  parallel (0.5.1)
13
21
  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)
22
+ rspec (2.6.0)
23
+ rspec-core (~> 2.6.0)
24
+ rspec-expectations (~> 2.6.0)
25
+ rspec-mocks (~> 2.6.0)
26
+ rspec-core (2.6.4)
27
+ rspec-expectations (2.6.0)
28
+ diff-lcs (~> 1.1.2)
29
+ rspec-mocks (2.6.0)
30
+ term-ansicolor (1.0.7)
31
+ test-unit (2.4.4)
26
32
 
27
33
  PLATFORMS
28
34
  ruby
29
35
 
30
36
  DEPENDENCIES
37
+ cucumber
31
38
  jeweler
32
39
  parallel
33
40
  rake
34
- rspec (~> 2)
41
+ rspec (>= 2.4)
42
+ test-unit
data/Rakefile CHANGED
@@ -10,12 +10,12 @@ begin
10
10
  gem.name = "moxiesoft_parallel_tests"
11
11
  gem.summary = "Run tests / specs / features in parallel"
12
12
  gem.email = "grosser.michael@gmail.com"
13
- gem.homepage = "http://github.com/edebill-moxiesoft/#{gem.name}"
14
- gem.authors = ['Michael Grosser', 'Erik DeBill']
13
+ gem.homepage = "http://github.com/ssherman/#{gem.name}"
14
+ gem.authors = ['Michael Grosser', 'Eric DeBill']
15
15
  gem.add_dependency "parallel"
16
16
  end
17
17
 
18
18
  Jeweler::GemcutterTasks.new
19
19
  rescue LoadError
20
20
  puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"
21
- end
21
+ end
data/Readme.md CHANGED
@@ -1,37 +1,53 @@
1
- Speedup Test::Unit + RSpec + Cucumber by running parallel on multiple CPUs(or cores).
1
+ Speedup Test::Unit + RSpec + Cucumber by running parallel on multiple CPUs (or cores).<br/>
2
+ ParallelTests splits tests into even groups(by number of tests or runtime) and runs each group in a single process with its own database.
2
3
 
3
4
  Setup for Rails
4
5
  ===============
5
-
6
6
  ## Install
7
7
  ### Rails 3
8
+ If you use RSpec: ensure you got >= 2.4
9
+
8
10
  As gem
9
- sudo gem install parallel_tests
11
+
10
12
  # add to Gemfile
11
- gem "parallel_tests", :group=>:development
13
+ gem "parallel_tests", :group => :development
12
14
 
13
15
  OR as plugin
14
- sudo gem install parallel
16
+
15
17
  rails plugin install git://github.com/grosser/parallel_tests.git
16
18
 
19
+ # add to Gemfile
20
+ gem "parallel", :group => :development
21
+
22
+
17
23
  ### Rails 2
18
24
 
19
25
  As gem
20
- sudo gem install parallel_tests
26
+
27
+ gem install parallel_tests
28
+
21
29
  # add to config/environments/development.rb
22
30
  config.gem "parallel_tests"
31
+
23
32
  # add to Rakefile
24
33
  begin; require 'parallel_tests/tasks'; rescue LoadError; end
25
34
 
26
35
  OR as plugin
27
36
 
28
- sudo gem install parallel
37
+ gem install parallel
38
+
39
+ # add to config/environments/development.rb
40
+ config.gem "parallel"
41
+
29
42
  ./script/plugin install git://github.com/grosser/parallel_tests.git
30
43
 
31
44
  ## Setup
45
+ ParallelTests uses 1 database per test-process, 2 processes will use `*_test` and `*_test2`.
46
+
47
+
32
48
  ### 1: Add to `config/database.yml`
33
49
  test:
34
- database: xxx_test<%= ENV['TEST_ENV_NUMBER'] %>
50
+ database: yourproject_test<%= ENV['TEST_ENV_NUMBER'] %>
35
51
 
36
52
  ### 2: Create additional database(s)
37
53
  rake parallel:create
@@ -49,29 +65,80 @@ OR as plugin
49
65
  rake parallel:test --> got 4 CPUs? --> 26 seconds
50
66
  ...
51
67
 
52
- Test just a subfolder (e.g. use one integration server per subfolder)
53
- rake parallel:test[models]
54
- rake parallel:test[something/else]
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
55
73
 
56
- partial paths are OK too...
57
- rake parallel:test[functional] == rake parallel:test[fun]
58
74
 
59
75
  Example output
60
76
  --------------
61
77
  2 processes for 210 specs, ~ 105 specs per process
62
78
  ... test output ...
63
79
 
64
- Results:
65
- 877 examples, 0 failures, 11 pending
66
80
  843 examples, 0 failures, 1 pending
67
81
 
68
82
  Took 29.925333 seconds
69
83
 
70
- Even process runtimes (for specs only atm)
84
+ Loggers
85
+ ===================
86
+
87
+ Even process runtimes
71
88
  -----------------
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.
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
75
142
 
76
143
  Setup for non-rails
77
144
  ===================
@@ -81,9 +148,11 @@ Setup for non-rails
81
148
  # [Optional] use ENV['TEST_ENV_NUMBER'] inside your tests to select separate db/memcache/etc.
82
149
 
83
150
  [optional] Only run selected files & folders:
84
- parallel_test test/bar test/baz/xxx_text.rb
151
+
152
+ parallel_test test/bar test/baz/foo_text.rb
85
153
 
86
154
  Options are:
155
+
87
156
  -n [PROCESSES] How many processes to use, default: available CPUs
88
157
  -p, --path [PATH] run tests inside this path only
89
158
  --no-sort do not sort files before running them
@@ -97,6 +166,7 @@ Options are:
97
166
  -h, --help Show this.
98
167
 
99
168
  You can run any kind of code with -e / --execute
169
+
100
170
  parallel_test -n 5 -e 'ruby -e "puts %[hello from process #{ENV[:TEST_ENV_NUMBER.to_s].inspect}]"'
101
171
  hello from process "2"
102
172
  hello from process ""
@@ -118,10 +188,14 @@ TIPS
118
188
  - [RSpec] if `script/spec` is missing parallel:spec uses just `spec` (which solves some issues with double-loaded environment.rb)
119
189
  - [RSpec] 'script/spec_server' or [spork](http://github.com/timcharper/spork/tree/master) do not work in parallel
120
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
121
192
  - [Bundler] if you have a `Gemfile` then `bundle exec` will be used to run tests
122
193
  - [Capybara setup](https://github.com/grosser/parallel_tests/wiki)
123
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
124
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
125
199
  - with zsh this would be `rake "parallel:prepare[3]"`
126
200
 
127
201
  TODO
@@ -150,7 +224,17 @@ inspired by [pivotal labs](http://pivotallabs.com/users/miked/blog/articles/849-
150
224
  - [Fred Wu](http://fredwu.me)
151
225
  - [xxx](https://github.com/xxx)
152
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)
153
236
 
154
237
  [Michael Grosser](http://grosser.it)<br/>
155
238
  michael@grosser.it<br/>
156
- Hereby placed under public domain, do what you want, just do not hold me accountable...
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 CHANGED
@@ -1 +1 @@
1
- 0.4.13
1
+ 0.6.18
data/bin/parallel_test CHANGED
@@ -17,18 +17,25 @@ Run all tests in parallel, giving each process ENV['TEST_ENV_NUMBER'] ('', '2',
17
17
  Options are:
18
18
  BANNER
19
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 }
20
+ opts.on("-p", '--pattern [PATTERN]', "run tests matching this pattern"){|pattern| options[:pattern] = pattern }
21
21
  opts.on("--no-sort", "do not sort files before running them"){ |no_sort| options[:no_sort] = no_sort }
22
22
  opts.on("-m [FLOAT]", "--multiply-processes [FLOAT]", Float, "use given number as a multiplier of processes to run"){ |multiply| options[:multiply] = multiply }
23
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
24
28
  opts.on("-e", '--exec [COMMAND]', "execute this code parallel and with ENV['TEST_ENV_NUM']"){|path| options[:execute] = path }
25
29
  opts.on("-o", "--test-options '[OPTIONS]'", "execute test commands with those options"){|arg| options[:test_options] = arg }
26
30
  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 }
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 }
28
33
  opts.on('-v', '--version', 'Show Version'){ puts ParallelTests::VERSION; exit}
29
34
  opts.on("-h", "--help", "Show this.") { puts opts; exit }
30
35
  end.parse!
31
36
 
37
+ raise "--no-sort and --single-process are not supported" if options[:no_sort] and options[:single_process]
38
+
32
39
  # get files to run from arguments
33
40
  options[:files] = ARGV if ARGV.size > 0
34
41
 
@@ -39,11 +46,11 @@ if options[:execute]
39
46
  runs = (0...num_processes).to_a
40
47
  results = if options[:non_parallel]
41
48
  runs.map do |i|
42
- ParallelTests.execute_command(options[:execute], i)
49
+ ParallelTests.execute_command(options[:execute], i, options)
43
50
  end
44
51
  else
45
52
  Parallel.map(runs, :in_processes => num_processes) do |i|
46
- ParallelTests.execute_command(options[:execute], i)
53
+ ParallelTests.execute_command(options[:execute], i, options)
47
54
  end
48
55
  end.flatten
49
56
  abort if results.any?{|r| r[:exit_status] != 0 }
@@ -59,17 +66,10 @@ else
59
66
 
60
67
  start = Time.now
61
68
 
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
+ tests_folder = task
70
+ tests_folder = File.join(options[:root], tests_folder) unless options[:root].to_s.empty?
69
71
 
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])
72
+ groups = klass.tests_in_groups(options[:files] || tests_folder, num_processes, options)
73
73
  num_processes = groups.size
74
74
 
75
75
  #adjust processes to groups
@@ -79,14 +79,17 @@ else
79
79
  puts "#{num_processes} processes for #{num_tests} #{name}s, ~ #{num_tests / groups.size} #{name}s per process"
80
80
 
81
81
  test_results = Parallel.map(groups, :in_processes => num_processes) do |group|
82
- klass.run_tests(group, groups.index(group), options[:test_options])
82
+ if group.empty?
83
+ {:stdout => '', :exit_status => 0}
84
+ else
85
+ klass.run_tests(group, groups.index(group), options)
86
+ end
83
87
  end
84
88
 
85
89
  #parse and print results
86
90
  results = klass.find_results(test_results.map{|result| result[:stdout] }*"")
87
91
  puts ""
88
- puts "Results:"
89
- results.each{|r| puts r }
92
+ puts klass.summarize_results(results)
90
93
 
91
94
  #report total time taken
92
95
  puts ""
@@ -95,4 +98,4 @@ else
95
98
  #exit with correct status code so rake parallel:test && echo 123 works
96
99
  failed = test_results.any?{|result| result[:exit_status] != 0 }
97
100
  abort "#{name.capitalize}s Failed" if failed
98
- end
101
+ end
@@ -1,10 +1,13 @@
1
- require File.join(File.dirname(__FILE__), 'parallel_tests')
1
+ require 'parallel_tests'
2
2
 
3
3
  class ParallelCucumber < ParallelTests
4
4
  def self.run_tests(test_files, process_number, options)
5
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)
6
+ runtime_logging = " --format ParallelCucumber::RuntimeLogger --out #{runtime_log}"
7
+ cmd = "#{color} #{executable}"
8
+ cmd << runtime_logging if File.directory?(File.dirname(runtime_log))
9
+ cmd << " #{options[:test_options]} #{test_files*' '}"
10
+ execute_command(cmd, process_number, options)
8
11
  end
9
12
 
10
13
  def self.executable
@@ -17,6 +20,10 @@ class ParallelCucumber < ParallelTests
17
20
  end
18
21
  end
19
22
 
23
+ def self.runtime_log
24
+ 'tmp/parallel_runtime_cucumber.log'
25
+ end
26
+
20
27
  protected
21
28
 
22
29
  def self.test_suffix
@@ -1,14 +1,8 @@
1
- require 'spec/runner/formatter/progress_bar_formatter'
1
+ require 'parallel_specs/spec_logger_base'
2
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
3
+ class ParallelSpecs::SpecRuntimeLogger < ParallelSpecs::SpecLoggerBase
4
+ def initialize(*args)
5
+ super
12
6
  @example_times = Hash.new(0)
13
7
  end
14
8
 
@@ -21,29 +15,20 @@ class ParallelSpecs::SpecRuntimeLogger < Spec::Runner::Formatter::BaseTextFormat
21
15
  @example_times[file] += Time.now - @time
22
16
  end
23
17
 
18
+ def dump_summary(*args);end
19
+ def dump_failures(*args);end
20
+ def dump_failure(*args);end
21
+ def dump_pending(*args);end
22
+
24
23
  def start_dump(*args)
25
24
  return unless ENV['TEST_ENV_NUMBER'] #only record when running in parallel
26
25
  # TODO: Figure out why sometimes time can be less than 0
27
- @output.puts @example_times.map { |file, time| "#{file}:#{time > 0 ? time : 0}" }
26
+ lock_output do
27
+ @example_times.each do |file, time|
28
+ relative_path = file.sub(/^#{Regexp.escape Dir.pwd}\//,'')
29
+ @output.puts "#{relative_path}:#{time > 0 ? time : 0}"
30
+ end
31
+ end
28
32
  @output.flush
29
33
  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
34
+ end
@@ -1,10 +1,11 @@
1
- require File.join(File.dirname(__FILE__), 'parallel_tests')
1
+ require 'parallel_tests'
2
2
 
3
3
  class ParallelSpecs < ParallelTests
4
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)
5
+ exe = executable # expensive, so we cache
6
+ version = (exe =~ /\brspec\b/ ? 2 : 1)
7
+ cmd = "#{rspec_1_color if version == 1}#{exe} #{options[:test_options]} #{rspec_2_color if version == 2}#{spec_opts(version)} #{test_files*' '}"
8
+ execute_command(cmd, process_number, options)
8
9
  end
9
10
 
10
11
  def self.executable
@@ -19,6 +20,11 @@ class ParallelSpecs < ParallelTests
19
20
  cmd or raise("Can't find executables rspec or spec")
20
21
  end
21
22
 
23
+ # legacy <-> people log to this file using rspec options
24
+ def self.runtime_log
25
+ 'tmp/parallel_profile.log'
26
+ end
27
+
22
28
  protected
23
29
 
24
30
  # so it can be stubbed....
@@ -26,20 +32,18 @@ class ParallelSpecs < ParallelTests
26
32
  `#{cmd}`
27
33
  end
28
34
 
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
35
+ def self.rspec_1_color
36
+ 'RSPEC_COLOR=1 ; export RSPEC_COLOR ;' if $stdout.tty?
37
+ end
38
+
39
+ def self.rspec_2_color
40
+ '--color --tty ' if $stdout.tty?
38
41
  end
39
42
 
40
- #display color when we are in a terminal
41
- def self.color
42
- ($stdout.tty? ? 'RSPEC_COLOR=1 ; export RSPEC_COLOR ;' : '')
43
+ def self.spec_opts(rspec_version)
44
+ options_file = ['.rspec_parallel', 'spec/parallel_spec.opts', 'spec/spec.opts'].detect{|f| File.file?(f) }
45
+ return unless options_file
46
+ "-O #{options_file}"
43
47
  end
44
48
 
45
49
  def self.test_suffix
@@ -1,31 +1,49 @@
1
1
  class ParallelTests
2
2
  class Grouper
3
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
4
+ groups = Array.new(num_groups){ [] }
5
+
6
+ until items.empty?
7
+ num_groups.times do |group_number|
8
+ groups[group_number] << items.shift
10
9
  end
11
10
  end
11
+
12
+ groups.map!(&:sort!)
12
13
  end
13
14
 
14
- def self.in_even_groups_by_size(items_with_sizes, num_groups)
15
- items_with_size = smallest_first(items_with_sizes)
15
+ def self.in_even_groups_by_size(items_with_sizes, num_groups, options={})
16
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
17
+
18
+ # add all files that should run in a single process to one group
19
+ (options[:single_process]||[]).each do |pattern|
20
+ matched, items_with_sizes = items_with_sizes.partition{|item, size| item =~ pattern }
21
+ smallest = smallest_group(groups)
22
+ matched.each{|item,size| add_to_group(smallest, item, size) }
23
+ end
24
+
25
+ # add all other files
26
+ largest_first(items_with_sizes).each do |item, size|
27
+ smallest = smallest_group(groups)
28
+ add_to_group(smallest, item, size)
22
29
  end
23
30
 
24
- groups.map{|g| g[:items] }
31
+ groups.map!{|g| g[:items].sort }
25
32
  end
26
33
 
27
- def self.smallest_first(files)
34
+ def self.largest_first(files)
28
35
  files.sort_by{|item, size| size }.reverse
29
36
  end
37
+
38
+ private
39
+
40
+ def self.smallest_group(groups)
41
+ groups.min_by{|g| g[:size] }
42
+ end
43
+
44
+ def self.add_to_group(group, item, size)
45
+ group[:items] << item
46
+ group[:size] += size
47
+ end
30
48
  end
31
- end
49
+ end
@@ -18,7 +18,7 @@ namespace :parallel do
18
18
 
19
19
  desc "update test databases by dumping and loading --> parallel:prepare[num_cpus]"
20
20
  task(:prepare, [:count] => 'db:abort_if_pending_migrations') do |t,args|
21
- if ActiveRecord::Base.schema_format == :ruby
21
+ if defined?(ActiveRecord) && ActiveRecord::Base.schema_format == :ruby
22
22
  # dump then load in parallel
23
23
  Rake::Task['db:schema:dump'].invoke
24
24
  Rake::Task['parallel:load_schema'].invoke(args[:count])
@@ -30,7 +30,7 @@ namespace :parallel do
30
30
  end
31
31
 
32
32
  # when dumping/resetting takes too long
33
- desc "update test databases via db:mgrate --> parallel:migrate[num_cpus]"
33
+ desc "update test databases via db:migrate --> parallel:migrate[num_cpus]"
34
34
  task :migrate, :count do |t,args|
35
35
  run_in_parallel('rake db:migrate RAILS_ENV=test', args)
36
36
  end
@@ -43,12 +43,12 @@ namespace :parallel do
43
43
 
44
44
  ['test', 'spec', 'features'].each do |type|
45
45
  desc "run #{type} in parallel with parallel:#{type}[num_cpus]"
46
- task type, :count, :path_prefix, :options do |t,args|
46
+ task type, [:count, :pattern, :options] => 'db:abort_if_pending_migrations' do |t,args|
47
47
  $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..'))
48
48
  require "parallel_tests"
49
- count, prefix, options = ParallelTests.parse_rake_args(args)
49
+ count, pattern, options = ParallelTests.parse_rake_args(args)
50
50
  executable = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'parallel_test')
51
- command = "#{executable} --type #{type} -n #{count} -p '#{prefix}' -r '#{Rails.root}' -o '#{options}'"
51
+ command = "#{executable} --type #{type} -n #{count} -p '#{pattern}' -r '#{Rails.root}' -o '#{options}'"
52
52
  abort unless system(command) # allow to chain tasks e.g. rake parallel:spec parallel:features
53
53
  end
54
54
  end
@@ -66,15 +66,15 @@ namespace :spec do
66
66
  end
67
67
  end
68
68
 
69
- task :parallel, :count, :path_prefix do |t,args|
69
+ task :parallel, :count, :pattern do |t,args|
70
70
  $stderr.puts "WARNING -- Deprecated! use parallel:spec"
71
- Rake::Task['parallel:spec'].invoke(args[:count], args[:path_prefix])
71
+ Rake::Task['parallel:spec'].invoke(args[:count], args[:pattern])
72
72
  end
73
73
  end
74
74
 
75
75
  namespace :test do
76
- task :parallel, :count, :path_prefix do |t,args|
76
+ task :parallel, :count, :pattern do |t,args|
77
77
  $stderr.puts "WARNING -- Deprecated! use parallel:test"
78
- Rake::Task['parallel:test'].invoke(args[:count], args[:path_prefix])
78
+ Rake::Task['parallel:test'].invoke(args[:count], args[:pattern])
79
79
  end
80
80
  end