vinted-parallel_tests 0.13.3
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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +48 -0
- data/Rakefile +6 -0
- data/Readme.md +293 -0
- data/ReadmeRails2.md +48 -0
- data/bin/parallel_cucumber +5 -0
- data/bin/parallel_rspec +5 -0
- data/bin/parallel_test +5 -0
- data/lib/parallel_tests/cli.rb +187 -0
- data/lib/parallel_tests/cucumber/failures_logger.rb +25 -0
- data/lib/parallel_tests/cucumber/gherkin_listener.rb +82 -0
- data/lib/parallel_tests/cucumber/io.rb +41 -0
- data/lib/parallel_tests/cucumber/runner.rb +98 -0
- data/lib/parallel_tests/cucumber/runtime_logger.rb +28 -0
- data/lib/parallel_tests/grouper.rb +56 -0
- data/lib/parallel_tests/railtie.rb +8 -0
- data/lib/parallel_tests/rspec/failures_logger.rb +44 -0
- data/lib/parallel_tests/rspec/logger_base.rb +52 -0
- data/lib/parallel_tests/rspec/runner.rb +72 -0
- data/lib/parallel_tests/rspec/runtime_logger.rb +54 -0
- data/lib/parallel_tests/rspec/summary_logger.rb +19 -0
- data/lib/parallel_tests/tasks.rb +139 -0
- data/lib/parallel_tests/test/runner.rb +168 -0
- data/lib/parallel_tests/test/runtime_logger.rb +97 -0
- data/lib/parallel_tests/version.rb +3 -0
- data/lib/parallel_tests.rb +61 -0
- data/parallel_tests.gemspec +14 -0
- data/spec/integration_spec.rb +285 -0
- data/spec/parallel_tests/cli_spec.rb +71 -0
- data/spec/parallel_tests/cucumber/failure_logger_spec.rb +43 -0
- data/spec/parallel_tests/cucumber/gherkin_listener_spec.rb +97 -0
- data/spec/parallel_tests/cucumber/runner_spec.rb +179 -0
- data/spec/parallel_tests/grouper_spec.rb +52 -0
- data/spec/parallel_tests/rspec/failures_logger_spec.rb +82 -0
- data/spec/parallel_tests/rspec/runner_spec.rb +187 -0
- data/spec/parallel_tests/rspec/runtime_logger_spec.rb +126 -0
- data/spec/parallel_tests/rspec/summary_logger_spec.rb +37 -0
- data/spec/parallel_tests/tasks_spec.rb +151 -0
- data/spec/parallel_tests/test/runner_spec.rb +413 -0
- data/spec/parallel_tests/test/runtime_logger_spec.rb +90 -0
- data/spec/parallel_tests_spec.rb +137 -0
- data/spec/spec_helper.rb +157 -0
- metadata +110 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
require 'rake'
|
|
2
|
+
|
|
3
|
+
module ParallelTests
|
|
4
|
+
module Tasks
|
|
5
|
+
class << self
|
|
6
|
+
def rails_env
|
|
7
|
+
ENV['RAILS_ENV'] || 'test'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def run_in_parallel(cmd, options={})
|
|
11
|
+
count = " -n #{options[:count]}" if options[:count]
|
|
12
|
+
executable = File.expand_path("../../../bin/parallel_test", __FILE__)
|
|
13
|
+
command = "#{executable} --exec '#{cmd}'#{count}#{' --non-parallel' if options[:non_parallel]}"
|
|
14
|
+
command << " --advance-number #{options[:advance_number]}" if options[:advance_number]
|
|
15
|
+
abort unless system(command)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# this is a crazy-complex solution for a very simple problem:
|
|
19
|
+
# removing certain lines from the output without chaning the exit-status
|
|
20
|
+
# normally I'd not do this, but it has been lots of fun and a great learning experience :)
|
|
21
|
+
#
|
|
22
|
+
# - sed does not support | without -r
|
|
23
|
+
# - grep changes 0 exitstatus to 1 if nothing matches
|
|
24
|
+
# - sed changes 1 exitstatus to 0
|
|
25
|
+
# - pipefail makes pipe fail with exitstatus of first failed command
|
|
26
|
+
# - pipefail is not supported in (zsh)
|
|
27
|
+
# - defining a new rake task like silence_schema would force users to load parallel_tests in test env
|
|
28
|
+
# - do not use ' since run_in_parallel uses them to quote stuff
|
|
29
|
+
# - simple system "set -o pipefail" returns nil even though set -o pipefail exists with 0
|
|
30
|
+
def suppress_output(command, ignore_regex)
|
|
31
|
+
activate_pipefail = "set -o pipefail"
|
|
32
|
+
remove_ignored_lines = %Q{(grep -v "#{ignore_regex}" || test 1)}
|
|
33
|
+
|
|
34
|
+
if system("#{activate_pipefail} 2>/dev/null && test 1")
|
|
35
|
+
"#{activate_pipefail} && (#{command}) | #{remove_ignored_lines}"
|
|
36
|
+
else
|
|
37
|
+
command
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def check_for_pending_migrations
|
|
42
|
+
abort_migrations = "db:abort_if_pending_migrations"
|
|
43
|
+
if Rake::Task.task_defined?(abort_migrations)
|
|
44
|
+
Rake::Task[abort_migrations].invoke
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# parallel:spec[:count, :pattern, :options]
|
|
49
|
+
def parse_args(args)
|
|
50
|
+
# order as given by user
|
|
51
|
+
args = [args[:count], args[:pattern], args[:options]]
|
|
52
|
+
|
|
53
|
+
# count given or empty ?
|
|
54
|
+
# parallel:spec[2,models,options]
|
|
55
|
+
# parallel:spec[,models,options]
|
|
56
|
+
count = args.shift if args.first.to_s =~ /^\d*$/
|
|
57
|
+
num_processes = count.to_i unless count.to_s.empty?
|
|
58
|
+
pattern = args.shift
|
|
59
|
+
options = args.shift
|
|
60
|
+
|
|
61
|
+
[num_processes, pattern.to_s, options.to_s]
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
namespace :parallel do
|
|
68
|
+
desc "create test databases via db:create --> parallel:create[num_cpus]"
|
|
69
|
+
task :create, :count do |t,args|
|
|
70
|
+
ParallelTests::Tasks.run_in_parallel("rake db:create RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
desc "drop test databases via db:drop --> parallel:drop[num_cpus]"
|
|
74
|
+
task :drop, :count do |t,args|
|
|
75
|
+
ParallelTests::Tasks.run_in_parallel("rake db:drop RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
desc "update test databases by dumping and loading --> parallel:prepare[num_cpus]"
|
|
79
|
+
task(:prepare, [:count]) do |t,args|
|
|
80
|
+
ParallelTests::Tasks.check_for_pending_migrations
|
|
81
|
+
if defined?(ActiveRecord) && ActiveRecord::Base.schema_format == :ruby
|
|
82
|
+
# dump then load in parallel
|
|
83
|
+
Rake::Task['db:schema:dump'].invoke
|
|
84
|
+
Rake::Task['parallel:load_schema'].invoke(args[:count])
|
|
85
|
+
else
|
|
86
|
+
# there is no separate dump / load for schema_format :sql -> do it safe and slow
|
|
87
|
+
args = args.to_hash.merge(:non_parallel => true) # normal merge returns nil
|
|
88
|
+
ParallelTests::Tasks.run_in_parallel('rake db:test:prepare --trace', args)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# when dumping/resetting takes too long
|
|
93
|
+
desc "update test databases via db:migrate --> parallel:migrate[num_cpus]"
|
|
94
|
+
task :migrate, :count do |t,args|
|
|
95
|
+
ParallelTests::Tasks.run_in_parallel("rake db:migrate RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# just load the schema (good for integration server <-> no development db)
|
|
99
|
+
desc "load dumped schema for test databases via db:schema:load --> parallel:load_schema[num_cpus]"
|
|
100
|
+
task :load_schema, :count do |t,args|
|
|
101
|
+
command = "rake db:schema:load RAILS_ENV=#{ParallelTests::Tasks.rails_env}"
|
|
102
|
+
ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_output(command, "^ ->\\|^-- "), args)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
desc "load the seed data from db/seeds.rb via db:seed --> parallel:seed[num_cpus]"
|
|
106
|
+
task :seed, :count do |t,args|
|
|
107
|
+
ParallelTests::Tasks.run_in_parallel("rake db:seed RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
desc "launch given rake command in parallel"
|
|
111
|
+
task :rake, :command do |t, args|
|
|
112
|
+
ParallelTests::Tasks.run_in_parallel("RAILS_ENV=#{ParallelTests::Tasks.rails_env} rake #{args.command}")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
['test', 'spec', 'features'].each do |type|
|
|
116
|
+
desc "run #{type} in parallel with parallel:#{type}[num_cpus]"
|
|
117
|
+
task type, [:count, :pattern, :options] do |t, args|
|
|
118
|
+
ParallelTests::Tasks.check_for_pending_migrations
|
|
119
|
+
|
|
120
|
+
$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
|
121
|
+
require "parallel_tests"
|
|
122
|
+
|
|
123
|
+
count, pattern, options = ParallelTests::Tasks.parse_args(args)
|
|
124
|
+
test_framework = {
|
|
125
|
+
'spec' => 'rspec',
|
|
126
|
+
'test' => 'test',
|
|
127
|
+
'features' => 'cucumber'
|
|
128
|
+
}[type]
|
|
129
|
+
|
|
130
|
+
executable = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'parallel_test')
|
|
131
|
+
command = "#{executable} #{type} --type #{test_framework} " \
|
|
132
|
+
"-n #{count} " \
|
|
133
|
+
"--pattern '#{pattern}' " \
|
|
134
|
+
"--test-options '#{options}'"
|
|
135
|
+
|
|
136
|
+
abort unless system(command) # allow to chain tasks e.g. rake parallel:spec parallel:features
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
require 'open3'
|
|
2
|
+
|
|
3
|
+
module ParallelTests
|
|
4
|
+
module Test
|
|
5
|
+
class Runner
|
|
6
|
+
NAME = 'Test'
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
# --- usually overwritten by other runners
|
|
10
|
+
|
|
11
|
+
def name
|
|
12
|
+
NAME
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def runtime_log
|
|
16
|
+
'tmp/parallel_runtime_test.log'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_suffix
|
|
20
|
+
"_test.rb"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_file_name
|
|
24
|
+
"test"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def run_tests(test_files, process_number, num_processes, options)
|
|
28
|
+
require_list = test_files.map { |filename| %{"#{File.expand_path filename}"} }.join(",")
|
|
29
|
+
cmd = "#{executable} -Itest -e '[#{require_list}].each {|f| require f }' -- #{options[:test_options]}"
|
|
30
|
+
execute_command(cmd, process_number, num_processes, options)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def line_is_result?(line)
|
|
34
|
+
line =~ /\d+ failure/
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# --- usually used by other runners
|
|
38
|
+
|
|
39
|
+
# finds all tests and partitions them into groups
|
|
40
|
+
def tests_in_groups(tests, num_groups, options={})
|
|
41
|
+
tests = find_tests(tests, options)
|
|
42
|
+
|
|
43
|
+
tests = if options[:group_by] == :found
|
|
44
|
+
tests.map { |t| [t, 1] }
|
|
45
|
+
else
|
|
46
|
+
with_runtime_info(tests)
|
|
47
|
+
end
|
|
48
|
+
Grouper.in_even_groups_by_size(tests, num_groups, options)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def execute_command(cmd, process_number, num_processes, options)
|
|
52
|
+
env = (options[:env] || {}).merge(
|
|
53
|
+
"TEST_ENV_NUMBER" => test_env_number(process_number, options),
|
|
54
|
+
"PARALLEL_TEST_GROUPS" => num_processes
|
|
55
|
+
)
|
|
56
|
+
cmd = "nice #{cmd}" if options[:nice]
|
|
57
|
+
execute_command_and_capture_output(env, cmd, options[:serialize_stdout])
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def execute_command_and_capture_output(env, cmd, silence)
|
|
61
|
+
# make processes descriptive / visible in ps -ef
|
|
62
|
+
exports = env.map do |k,v|
|
|
63
|
+
"#{k}=#{v};export #{k}"
|
|
64
|
+
end.join(";")
|
|
65
|
+
cmd = "#{exports};#{cmd}"
|
|
66
|
+
|
|
67
|
+
output = open("|#{cmd}", "r") { |output| capture_output(output, silence) }
|
|
68
|
+
exitstatus = $?.exitstatus
|
|
69
|
+
|
|
70
|
+
{:stdout => output, :exit_status => exitstatus}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def find_results(test_output)
|
|
74
|
+
test_output.split("\n").map {|line|
|
|
75
|
+
line = line.gsub(/\.|F|\*/,'').gsub(/\e\[\d+m/,'')
|
|
76
|
+
next unless line_is_result?(line)
|
|
77
|
+
line
|
|
78
|
+
}.compact
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def test_env_number(process_number, options)
|
|
82
|
+
n = options[:advance_number].to_i + process_number + 1
|
|
83
|
+
n == 0 ? '' : n
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def summarize_results(results)
|
|
87
|
+
sums = sum_up_results(results)
|
|
88
|
+
sums.sort.map{|word, number| "#{number} #{word}#{'s' if number != 1}" }.join(', ')
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
protected
|
|
92
|
+
|
|
93
|
+
def executable
|
|
94
|
+
ENV['PARALLEL_TESTS_EXECUTABLE'] || determine_executable
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def determine_executable
|
|
98
|
+
"ruby"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def sum_up_results(results)
|
|
102
|
+
results = results.join(' ').gsub(/s\b/,'') # combine and singularize results
|
|
103
|
+
counts = results.scan(/(\d+) (\w+)/)
|
|
104
|
+
counts.inject(Hash.new(0)) do |sum, (number, word)|
|
|
105
|
+
sum[word] += number.to_i
|
|
106
|
+
sum
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# read output of the process and print it in chunks
|
|
111
|
+
def capture_output(out, silence)
|
|
112
|
+
result = ""
|
|
113
|
+
loop do
|
|
114
|
+
begin
|
|
115
|
+
read = out.readpartial(1000000) # read whatever chunk we can get
|
|
116
|
+
result << read
|
|
117
|
+
unless silence
|
|
118
|
+
$stdout.print read
|
|
119
|
+
$stdout.flush
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end rescue EOFError
|
|
123
|
+
result
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def with_runtime_info(tests)
|
|
127
|
+
lines = File.read(runtime_log).split("\n") rescue []
|
|
128
|
+
|
|
129
|
+
# use recorded test runtime if we got enough data
|
|
130
|
+
if lines.size * 1.5 > tests.size
|
|
131
|
+
puts "Using recorded test runtime"
|
|
132
|
+
times = Hash.new(1)
|
|
133
|
+
lines.each do |line|
|
|
134
|
+
test, time = line.split(":")
|
|
135
|
+
next unless test and time
|
|
136
|
+
times[File.expand_path(test)] = time.to_f
|
|
137
|
+
end
|
|
138
|
+
tests.sort.map{|test| [test, times[File.expand_path(test)]] }
|
|
139
|
+
else # use file sizes
|
|
140
|
+
tests.sort.map{|test| [test, File.stat(test).size] }
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def find_tests(tests, options = {})
|
|
145
|
+
(tests || []).map do |file_or_folder|
|
|
146
|
+
if File.directory?(file_or_folder)
|
|
147
|
+
files = files_in_folder(file_or_folder, options)
|
|
148
|
+
files.grep(/#{Regexp.escape test_suffix}$/).grep(options[:pattern]||//)
|
|
149
|
+
else
|
|
150
|
+
file_or_folder
|
|
151
|
+
end
|
|
152
|
+
end.flatten.uniq
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def files_in_folder(folder, options={})
|
|
156
|
+
pattern = if options[:symlinks] == false # not nil or true
|
|
157
|
+
"**/*"
|
|
158
|
+
else
|
|
159
|
+
# follow one symlink and direct children
|
|
160
|
+
# http://stackoverflow.com/questions/357754/can-i-traverse-symlinked-directories-in-ruby-with-a-glob
|
|
161
|
+
"**{,/*/**}/*"
|
|
162
|
+
end
|
|
163
|
+
Dir[File.join(folder, pattern)].uniq
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
require 'parallel_tests'
|
|
2
|
+
require 'parallel_tests/test/runner'
|
|
3
|
+
|
|
4
|
+
module ParallelTests
|
|
5
|
+
module Test
|
|
6
|
+
class RuntimeLogger
|
|
7
|
+
@@has_started = false
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def log(test, start_time, end_time)
|
|
11
|
+
return if test.is_a? ::Test::Unit::TestSuite # don't log for suites-of-suites
|
|
12
|
+
|
|
13
|
+
if !@@has_started # make empty log file
|
|
14
|
+
File.open(logfile, 'w'){}
|
|
15
|
+
@@has_started = true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
locked_appending_to(logfile) do |file|
|
|
19
|
+
file.puts(message(test, start_time, end_time))
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def message(test, start_time, end_time)
|
|
26
|
+
delta = "%.2f" % (end_time.to_f-start_time.to_f)
|
|
27
|
+
filename = class_directory(test.class) + class_to_filename(test.class) + ".rb"
|
|
28
|
+
"#{filename}:#{delta}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Note: this is a best guess at conventional test directory structure, and may need
|
|
32
|
+
# tweaking / post-processing to match correctly for any given project
|
|
33
|
+
def class_directory(suspect)
|
|
34
|
+
result = "test/"
|
|
35
|
+
|
|
36
|
+
if defined?(Rails)
|
|
37
|
+
result += case suspect.superclass.name
|
|
38
|
+
when "ActionDispatch::IntegrationTest"
|
|
39
|
+
"integration/"
|
|
40
|
+
when "ActionDispatch::PerformanceTest"
|
|
41
|
+
"performance/"
|
|
42
|
+
when "ActionController::TestCase"
|
|
43
|
+
"functional/"
|
|
44
|
+
when "ActionView::TestCase"
|
|
45
|
+
"unit/helpers/"
|
|
46
|
+
else
|
|
47
|
+
"unit/"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
result
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# based on https://github.com/grosser/single_test/blob/master/lib/single_test.rb#L117
|
|
54
|
+
def class_to_filename(suspect)
|
|
55
|
+
word = suspect.to_s.dup
|
|
56
|
+
return word unless word.match /^[A-Z]/ and not word.match %r{/[a-z]}
|
|
57
|
+
|
|
58
|
+
word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
59
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
|
60
|
+
word.gsub!(/\:\:/, '/')
|
|
61
|
+
word.tr!("-", "_")
|
|
62
|
+
word.downcase!
|
|
63
|
+
word
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def locked_appending_to(file)
|
|
67
|
+
File.open(file, 'a') do |f|
|
|
68
|
+
begin
|
|
69
|
+
f.flock File::LOCK_EX
|
|
70
|
+
yield f
|
|
71
|
+
ensure
|
|
72
|
+
f.flock File::LOCK_UN
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def logfile
|
|
78
|
+
ParallelTests::Test::Runner.runtime_log
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
require 'test/unit/testsuite'
|
|
86
|
+
class ::Test::Unit::TestSuite
|
|
87
|
+
alias :run_without_timing :run unless defined? @@timing_installed
|
|
88
|
+
|
|
89
|
+
def run(result, &progress_block)
|
|
90
|
+
start_time = ParallelTests.now
|
|
91
|
+
run_without_timing(result, &progress_block)
|
|
92
|
+
end_time = ParallelTests.now
|
|
93
|
+
ParallelTests::Test::RuntimeLogger.log(self.tests.first, start_time, end_time)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
@@timing_installed = true
|
|
97
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
require "parallel"
|
|
2
|
+
require "parallel_tests/railtie" if defined? Rails::Railtie
|
|
3
|
+
|
|
4
|
+
module ParallelTests
|
|
5
|
+
GREP_PROCESSES_COMMAND = "ps -ef | grep [T]EST_ENV_NUMBER= 2>&1"
|
|
6
|
+
|
|
7
|
+
autoload :CLI, "parallel_tests/cli"
|
|
8
|
+
autoload :VERSION, "parallel_tests/version"
|
|
9
|
+
autoload :Grouper, "parallel_tests/grouper"
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
def determine_number_of_processes(count)
|
|
13
|
+
[
|
|
14
|
+
count,
|
|
15
|
+
ENV["PARALLEL_TEST_PROCESSORS"],
|
|
16
|
+
Parallel.processor_count
|
|
17
|
+
].detect{|c| not c.to_s.strip.empty? }.to_i
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# copied from http://github.com/carlhuda/bundler Bundler::SharedHelpers#find_gemfile
|
|
21
|
+
def bundler_enabled?
|
|
22
|
+
return true if Object.const_defined?(:Bundler)
|
|
23
|
+
|
|
24
|
+
previous = nil
|
|
25
|
+
current = File.expand_path(Dir.pwd)
|
|
26
|
+
|
|
27
|
+
until !File.directory?(current) || current == previous
|
|
28
|
+
filename = File.join(current, "Gemfile")
|
|
29
|
+
return true if File.exists?(filename)
|
|
30
|
+
current, previous = File.expand_path("..", current), current
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def first_process?
|
|
37
|
+
!ENV["TEST_ENV_NUMBER"] || ENV["TEST_ENV_NUMBER"].to_i == 0
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def wait_for_other_processes_to_finish
|
|
41
|
+
return unless ENV["TEST_ENV_NUMBER"]
|
|
42
|
+
sleep 1 until number_of_running_processes <= 1
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Fun fact: this includes the current process if it's run via parallel_tests
|
|
46
|
+
def number_of_running_processes
|
|
47
|
+
result = `#{GREP_PROCESSES_COMMAND}`
|
|
48
|
+
raise "Could not grep for processes -> #{result}" if result.strip != "" && !$?.success?
|
|
49
|
+
result.split("\n").size
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# real time even if someone messed with timecop in tests
|
|
53
|
+
def now
|
|
54
|
+
if Time.respond_to?(:now_without_mock_time) # Timecop
|
|
55
|
+
Time.now_without_mock_time
|
|
56
|
+
else
|
|
57
|
+
Time.now
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
|
2
|
+
name = "vinted-parallel_tests"
|
|
3
|
+
require "parallel_tests/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new name, ParallelTests::VERSION do |s|
|
|
6
|
+
s.summary = "Run Test::Unit / RSpec / Cucumber in parallel"
|
|
7
|
+
s.authors = ["Laurynas Butkus", "Tomas Varaneckas", "Justas Janauskas"]
|
|
8
|
+
s.email = ["laurynas.butkus@gmail.com", "tomas.varaneckas@gmail.com", "jjanauskas@gmail.com"]
|
|
9
|
+
s.homepage = "http://github.com/vinted/parallel_tests"
|
|
10
|
+
s.files = `git ls-files`.split("\n")
|
|
11
|
+
s.license = "MIT"
|
|
12
|
+
s.executables = ["parallel_cucumber", "parallel_rspec", "parallel_test"]
|
|
13
|
+
s.add_runtime_dependency "parallel"
|
|
14
|
+
end
|