parallelized_specs 0.0.8 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -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 CHANGED
@@ -1,30 +1,7 @@
1
1
  GEM
2
- remote: http://rubygems.org/
3
2
  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
3
 
22
4
  PLATFORMS
23
5
  ruby
24
6
 
25
7
  DEPENDENCIES
26
- jeweler
27
- parallel
28
- rake
29
- rspec (>= 2.4)
30
- test-unit
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ begin
12
12
  gem.email = "jake@instructure.com"
13
13
  gem.homepage = "http://github.com/jakesorce/#{gem.name}"
14
14
  gem.authors = "Jake Sorce, Bryan Madsen"
15
- gem.version = "0.0.8"
15
+ gem.version = "0.1.6"
16
16
  end
17
17
 
18
18
  Jeweler::GemcutterTasks.new
data/Readme.md CHANGED
@@ -56,9 +56,7 @@ ParallelizedSpecs uses 1 database per test-process, 2 processes will use `*_test
56
56
  rake parallel:prepare
57
57
 
58
58
  ### 4: Run!
59
- rake parallel:test # Test::Unit
60
59
  rake parallel:spec # RSpec
61
- rake parallel:features # Cucumber
62
60
 
63
61
  rake parallel:test[1] --> force 1 CPU --> 86 seconds
64
62
  rake parallel:test --> got 2 CPUs? --> 47 seconds
@@ -67,7 +65,7 @@ ParallelizedSpecs uses 1 database per test-process, 2 processes will use `*_test
67
65
 
68
66
  Test by pattern (e.g. use one integration server per subfolder / see if you broke any 'user'-related tests)
69
67
 
70
- rake parallel:test[^unit] # everything in test/unit folder (every test file matching /^unit/)
68
+ rake parallel:test[^unit] # everything in test/spec folder (every test file matching /^spec/)
71
69
  rake parallel:test[user] # run users_controller + user_helper + user tests
72
70
  rake parallel:test['user|product'] # run user and product related tests
73
71
 
@@ -151,35 +149,6 @@ Setup for non-rails
151
149
 
152
150
  parallelized_specs test/bar test/baz/xxx_text_spec.rb
153
151
 
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
152
  TIPS
184
153
  ====
185
154
  - [RSpec] add a `spec/parallel_spec.opts` to use different options, e.g. no --drb (default: `spec/spec.opts`)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.8
1
+ 0.1.6
@@ -12,9 +12,8 @@ class ParallelizedSpecs
12
12
  groups.map!(&:sort!)
13
13
  end
14
14
 
15
- def self.in_even_groups_by_size(items_with_sizes, num_groups, options={})
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
-
18
17
  # add all files that should run in a single process to one group
19
18
  (options[:single_process]||[]).each do |pattern|
20
19
  matched, items_with_sizes = items_with_sizes.partition{|item, size| item =~ pattern }
@@ -1,19 +1,19 @@
1
+ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..'))
2
+ require "parallelized_specs"
3
+
1
4
  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', 'parallelized_spec')
5
- command = "#{executable} --exec '#{cmd}' -n #{count} #{'--non-parallel' if options[:non_parallel]}"
6
- abort unless system(command)
5
+ def run_db_in_parallel(cmd, options={})
6
+ ParallelizedSpecs.execute_parallel_db(cmd, options)
7
7
  end
8
8
 
9
9
  desc "create test databases via db:create --> parallel:create[num_cpus]"
10
10
  task :create, :count do |t, args|
11
- run_in_parallel('rake db:create RAILS_ENV=test', args)
11
+ run_db_in_parallel('rake db:create RAILS_ENV=test', args)
12
12
  end
13
13
 
14
14
  desc "drop test databases via db:drop --> parallel:drop[num_cpus]"
15
15
  task :drop, :count do |t, args|
16
- run_in_parallel('rake db:drop RAILS_ENV=test', args)
16
+ run_db_in_parallel('rake db:drop RAILS_ENV=test', args)
17
17
  end
18
18
 
19
19
  desc "update test databases by dumping and loading --> parallel:prepare[num_cpus]"
@@ -25,46 +25,27 @@ namespace :parallel do
25
25
  else
26
26
  # there is no separate dump / load for schema_format :sql -> do it safe and slow
27
27
  args = args.to_hash.merge(:non_parallel => true) # normal merge returns nil
28
- run_in_parallel('rake db:test:prepare --trace', args)
28
+ run_db_in_parallel('rake db:test:prepare --trace', args)
29
29
  end
30
30
  end
31
31
 
32
32
  # when dumping/resetting takes too long
33
33
  desc "update test databases via db:migrate --> parallel:migrate[num_cpus]"
34
34
  task :migrate, :count do |t, args|
35
- run_in_parallel('rake db:migrate RAILS_ENV=test', args)
35
+ run_db_in_parallel('rake db:migrate RAILS_ENV=test', args)
36
36
  end
37
37
 
38
38
  # just load the schema (good for integration server <-> no development db)
39
39
  desc "load dumped schema for test databases via db:schema:load --> parallel:load_schema[num_cpus]"
40
40
  task :load_schema, :count do |t, args|
41
- run_in_parallel('rake db:test:load', args)
41
+ run_db_in_parallel('rake db:test:load', args)
42
42
  end
43
43
 
44
44
  desc "run spec in parallel with parallel:spec[num_cpus]"
45
45
  task 'spec', :count, :pattern, :options, :arguments do |t, args|
46
- $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..'))
47
- require "parallelized_specs"
48
46
  count, pattern, options = ParallelizedSpecs.parse_rake_args(args)
49
- executable = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'parallelized_spec')
50
- command = "#{executable} --type 'spec' -n #{count} -p '#{pattern}' -r '#{Rails.root}' -o '#{options}' #{args[:arguments]}"
51
- abort unless system(command) # allow to chain tasks e.g. rake parallel:spec
47
+ #command = "-n #{count} -p '#{pattern}' -r '#{Rails.root}' -o '#{options}' #{args[:arguments]}"
48
+ opts = Hash[:count => count, :pattern => pattern, :options => options, :root => Rails.root, :files => args[:arguments]]
49
+ ParallelizedSpecs.execute_parallel_specs(opts)
52
50
  end
53
51
  end
54
-
55
- #backwards compatability
56
- #spec:parallel:prepare
57
- #spec:parallel
58
- namespace :spec do
59
- namespace :parallel do
60
- task :prepare, :count do |t, args|
61
- $stderr.puts "WARNING -- Deprecated! use parallel:prepare"
62
- Rake::Task['parallel:prepare'].invoke(args[:count])
63
- end
64
- end
65
-
66
- task :parallel, :count, :pattern do |t, args|
67
- $stderr.puts "WARNING -- Deprecated! use parallel:spec"
68
- Rake::Task['parallel:spec'].invoke(args[:count], args[:pattern])
69
- end
70
- end
@@ -1,4 +1,6 @@
1
1
  require 'parallel'
2
+ raise "please ' gem install parallel '" if Gem::Version.new(Parallel::VERSION) < Gem::Version.new('0.4.2')
3
+ require 'optparse'
2
4
  require 'parallelized_specs/grouper'
3
5
  require 'parallelized_specs/railtie'
4
6
  require 'parallelized_specs/spec_error_logger'
@@ -51,6 +53,64 @@ class ParallelizedSpecs
51
53
  "_spec.rb"
52
54
  end
53
55
 
56
+ def self.execute_parallel_db(cmd, options={})
57
+ count = options[:count].to_i
58
+ count = Parallel.processor_count if count == 0
59
+ runs = (0...count).to_a
60
+ results = if options[:non_parallel]
61
+ runs.map do |i|
62
+ execute_command(cmd, i, options)
63
+ end
64
+ else
65
+ Parallel.map(runs, :in_processes => count) do |i|
66
+ execute_command(cmd, i, options)
67
+ end
68
+ end.flatten
69
+ abort if results.any? { |r| r[:exit_status] != 0 }
70
+ end
71
+
72
+ def self.execute_parallel_specs(options)
73
+ num_processes = options[:count] || Parallel.processor_count
74
+ lib, name, task = {
75
+ 'spec' => %w(specs spec spec),
76
+ }[options[:type]||'spec']
77
+
78
+ start = Time.now
79
+
80
+ tests_folder = task
81
+ tests_folder = File.join(options[:root], tests_folder) unless options[:root].to_s.empty?
82
+ if options[:files].is_a?(Array)
83
+ groups = tests_in_groups(options[:files] || tests_folder, num_processes, options)
84
+ else
85
+ files_array = options[:files].split(/ /)
86
+ groups = tests_in_groups(files_array || tests_folder, num_processes, options)
87
+ end
88
+ num_processes = groups.size
89
+
90
+ #adjust processes to groups
91
+ abort "no #{name}s found!" if groups.size == 0
92
+
93
+ num_tests = groups.inject(0) { |sum, item| sum + item.size }
94
+ puts "#{num_processes} processes for #{num_tests} #{name}s, ~ #{num_tests / groups.size} #{name}s per process"
95
+
96
+ test_results = Parallel.map(groups, :in_processes => num_processes) do |group|
97
+ run_tests(group, groups.index(group), options)
98
+ end
99
+
100
+ #parse and print results
101
+ results = find_results(test_results.map { |result| result[:stdout] }*"")
102
+ puts ""
103
+ puts summarize_results(results)
104
+
105
+ #report total time taken
106
+ puts ""
107
+ puts "Took #{Time.now - start} seconds"
108
+
109
+ #exit with correct status code so rake parallel:test && echo 123 works
110
+ failed = test_results.any? { |result| result[:exit_status] != 0 }
111
+ abort "#{name.capitalize}s Failed" if failed
112
+ end
113
+
54
114
  # parallel:spec[:count, :pattern, :options]
55
115
  def self.parse_rake_args(args)
56
116
  # order as given by user
@@ -71,7 +131,7 @@ class ParallelizedSpecs
71
131
  end
72
132
 
73
133
  # finds all tests and partitions them into groups
74
- def self.tests_in_groups(root, num_groups, options={})
134
+ def self.tests_in_groups(root, num_groups, options)
75
135
  tests = find_tests(root, options)
76
136
  if options[:no_sort]
77
137
  Grouper.in_groups(tests, num_groups)
@@ -184,15 +244,15 @@ class ParallelizedSpecs
184
244
  end
185
245
  end
186
246
 
187
- def self.find_tests(root, options={})
247
+ def self.find_tests(root, options)
188
248
  if root.is_a?(Array)
189
249
  root
190
250
  else
191
251
  # follow one symlink and direct children
192
252
  # http://stackoverflow.com/questions/357754/can-i-traverse-symlinked-directories-in-ruby-with-a-glob
193
- files = Dir["#{root}/**{,/*/**}/*#{test_suffix}"].uniq
253
+ files = Dir["#{Rails.root}/**{,/*/**}/*#{test_suffix}"].uniq
194
254
  files = files.map { |f| f.sub(root+'/', '') }
195
- files = files.grep(/#{options[:pattern]}/)
255
+ files = files.grep(/#{options['pattern']}/)
196
256
  files.map { |f| "#{root}/#{f}" }
197
257
  end
198
258
  end
@@ -5,20 +5,18 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "parallelized_specs"
8
- s.version = "0.0.8"
8
+ s.version = "0.1.6"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jake Sorce, Bryan Madsen"]
12
- s.date = "2012-04-23"
12
+ s.date = "2012-04-27"
13
13
  s.email = "jake@instructure.com"
14
- s.executables = ["parallelized_spec"]
15
14
  s.files = [
16
15
  "Gemfile",
17
16
  "Gemfile.lock",
18
17
  "Rakefile",
19
18
  "Readme.md",
20
19
  "VERSION",
21
- "bin/parallelized_spec",
22
20
  "lib/parallelized_specs.rb",
23
21
  "lib/parallelized_specs/grouper.rb",
24
22
  "lib/parallelized_specs/railtie.rb",
@@ -48,9 +46,12 @@ Gem::Specification.new do |s|
48
46
  s.specification_version = 3
49
47
 
50
48
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
49
+ s.add_runtime_dependency(%q<parallel>, [">= 0"])
51
50
  else
51
+ s.add_dependency(%q<parallel>, [">= 0"])
52
52
  end
53
53
  else
54
+ s.add_dependency(%q<parallel>, [">= 0"])
54
55
  end
55
56
  end
56
57
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parallelized_specs
3
3
  version: !ruby/object:Gem::Version
4
- hash: 15
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 0
9
- - 8
10
- version: 0.0.8
8
+ - 1
9
+ - 6
10
+ version: 0.1.6
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jake Sorce, Bryan Madsen
@@ -15,13 +15,26 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-04-23 00:00:00 Z
19
- dependencies: []
20
-
18
+ date: 2012-04-27 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: parallel
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
21
34
  description:
22
35
  email: jake@instructure.com
23
- executables:
24
- - parallelized_spec
36
+ executables: []
37
+
25
38
  extensions: []
26
39
 
27
40
  extra_rdoc_files: []
@@ -32,7 +45,6 @@ files:
32
45
  - Rakefile
33
46
  - Readme.md
34
47
  - VERSION
35
- - bin/parallelized_spec
36
48
  - lib/parallelized_specs.rb
37
49
  - lib/parallelized_specs/grouper.rb
38
50
  - lib/parallelized_specs/railtie.rb
@@ -1,95 +0,0 @@
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 "parallelized_specs"
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
- parallelized_spec test/bar test/baz/xxx_text_spec.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 ParallelizedSpecs::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
- ParallelizedSpecs.execute_command(options[:execute], i, options)
50
- end
51
- else
52
- Parallel.map(runs, :in_processes => num_processes) do |i|
53
- ParallelizedSpecs.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
- 'spec' => %w(specs spec spec),
60
- }[options[:type]||'spec']
61
-
62
- require "parallelized_#{lib}"
63
- klass = eval("Parallelized#{lib.capitalize}")
64
-
65
- start = Time.now
66
-
67
- tests_folder = task
68
- tests_folder = File.join(options[:root], tests_folder) unless options[:root].to_s.empty?
69
-
70
- groups = klass.tests_in_groups(options[:files] || tests_folder, num_processes, options)
71
- num_processes = groups.size
72
-
73
- #adjust processes to groups
74
- abort "no #{name}s found!" if groups.size == 0
75
-
76
- num_tests = groups.inject(0){|sum,item| sum + item.size }
77
- puts "#{num_processes} processes for #{num_tests} #{name}s, ~ #{num_tests / groups.size} #{name}s per process"
78
-
79
- test_results = Parallel.map(groups, :in_processes => num_processes) do |group|
80
- klass.run_tests(group, groups.index(group), options)
81
- end
82
-
83
- #parse and print results
84
- results = klass.find_results(test_results.map{|result| result[:stdout] }*"")
85
- puts ""
86
- puts klass.summarize_results(results)
87
-
88
- #report total time taken
89
- puts ""
90
- puts "Took #{Time.now - start} seconds"
91
-
92
- #exit with correct status code so rake parallel:test && echo 123 works
93
- failed = test_results.any?{|result| result[:exit_status] != 0 }
94
- abort "#{name.capitalize}s Failed" if failed
95
- end