moxiesoft_parallel_tests 0.4.13 → 0.6.18

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 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