vinted-parallel_tests 0.13.3 → 1.7.0.1
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 +4 -4
- data/Readme.md +86 -47
- data/bin/parallel_cucumber +5 -1
- data/bin/parallel_rspec +5 -1
- data/bin/parallel_spinach +9 -0
- data/bin/parallel_test +5 -1
- data/lib/parallel_tests.rb +15 -2
- data/lib/parallel_tests/cli.rb +115 -35
- data/lib/parallel_tests/cucumber/failures_logger.rb +9 -7
- data/lib/parallel_tests/cucumber/runner.rb +16 -77
- data/lib/parallel_tests/cucumber/scenario_line_logger.rb +52 -0
- data/lib/parallel_tests/cucumber/scenarios.rb +34 -0
- data/lib/parallel_tests/{cucumber → gherkin}/io.rb +1 -1
- data/lib/parallel_tests/{cucumber/gherkin_listener.rb → gherkin/listener.rb} +11 -6
- data/lib/parallel_tests/gherkin/runner.rb +116 -0
- data/lib/parallel_tests/{cucumber → gherkin}/runtime_logger.rb +2 -2
- data/lib/parallel_tests/grouper.rb +34 -17
- data/lib/parallel_tests/rspec/failures_logger.rb +22 -14
- data/lib/parallel_tests/rspec/logger_base.rb +5 -1
- data/lib/parallel_tests/rspec/runner.rb +5 -4
- data/lib/parallel_tests/rspec/runtime_logger.rb +10 -5
- data/lib/parallel_tests/rspec/summary_logger.rb +11 -11
- data/lib/parallel_tests/spinach/runner.rb +19 -0
- data/lib/parallel_tests/tasks.rb +41 -18
- data/lib/parallel_tests/test/runner.rb +80 -28
- data/lib/parallel_tests/test/runtime_logger.rb +86 -55
- data/lib/parallel_tests/version.rb +1 -1
- metadata +18 -35
- data/.gitignore +0 -2
- data/.rspec +0 -2
- data/.travis.yml +0 -6
- data/Gemfile +0 -8
- data/Gemfile.lock +0 -48
- data/Rakefile +0 -6
- data/ReadmeRails2.md +0 -48
- data/parallel_tests.gemspec +0 -14
- data/spec/integration_spec.rb +0 -285
- data/spec/parallel_tests/cli_spec.rb +0 -71
- data/spec/parallel_tests/cucumber/failure_logger_spec.rb +0 -43
- data/spec/parallel_tests/cucumber/gherkin_listener_spec.rb +0 -97
- data/spec/parallel_tests/cucumber/runner_spec.rb +0 -179
- data/spec/parallel_tests/grouper_spec.rb +0 -52
- data/spec/parallel_tests/rspec/failures_logger_spec.rb +0 -82
- data/spec/parallel_tests/rspec/runner_spec.rb +0 -187
- data/spec/parallel_tests/rspec/runtime_logger_spec.rb +0 -126
- data/spec/parallel_tests/rspec/summary_logger_spec.rb +0 -37
- data/spec/parallel_tests/tasks_spec.rb +0 -151
- data/spec/parallel_tests/test/runner_spec.rb +0 -413
- data/spec/parallel_tests/test/runtime_logger_spec.rb +0 -90
- data/spec/parallel_tests_spec.rb +0 -137
- data/spec/spec_helper.rb +0 -157
@@ -2,28 +2,36 @@ require 'parallel_tests/rspec/logger_base'
|
|
2
2
|
require 'parallel_tests/rspec/runner'
|
3
3
|
|
4
4
|
class ParallelTests::RSpec::FailuresLogger < ParallelTests::RSpec::LoggerBase
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
5
|
+
if RSPEC_1
|
6
|
+
# RSpec 1: does not keep track of failures, so we do
|
7
|
+
def example_failed(example, *args)
|
8
|
+
if RSPEC_1
|
9
|
+
@failed_examples ||= []
|
10
|
+
@failed_examples << example
|
11
|
+
else
|
12
|
+
super
|
13
|
+
end
|
12
14
|
end
|
13
|
-
end
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
def dump_failure(*args)
|
17
|
+
end
|
18
|
+
elsif RSPEC_2
|
19
|
+
def dump_failures(*args)
|
20
|
+
end
|
21
|
+
else
|
22
|
+
RSpec::Core::Formatters.register self, :dump_summary
|
21
23
|
end
|
22
24
|
|
23
25
|
def dump_summary(*args)
|
24
26
|
lock_output do
|
25
27
|
if RSPEC_1
|
26
28
|
dump_commands_to_rerun_failed_examples_rspec_1
|
29
|
+
elsif RSPEC_3
|
30
|
+
notification = args.first
|
31
|
+
unless notification.failed_examples.empty?
|
32
|
+
colorizer = ::RSpec::Core::Formatters::ConsoleCodes
|
33
|
+
output.puts notification.colorized_rerun_commands(colorizer)
|
34
|
+
end
|
27
35
|
else
|
28
36
|
dump_commands_to_rerun_failed_examples
|
29
37
|
end
|
@@ -15,6 +15,8 @@ ParallelTests::RSpec::LoggerBaseBase = base
|
|
15
15
|
|
16
16
|
class ParallelTests::RSpec::LoggerBase < ParallelTests::RSpec::LoggerBaseBase
|
17
17
|
RSPEC_1 = !defined?(RSpec::Core::Formatters::BaseTextFormatter) # do not test for Spec, this will trigger deprecation warning in rspec 2
|
18
|
+
RSPEC_2 = !RSPEC_1 && RSpec::Core::Version::STRING.start_with?('2')
|
19
|
+
RSPEC_3 = !RSPEC_1 && RSpec::Core::Version::STRING.start_with?('3')
|
18
20
|
|
19
21
|
def initialize(*args)
|
20
22
|
super
|
@@ -32,10 +34,12 @@ class ParallelTests::RSpec::LoggerBase < ParallelTests::RSpec::LoggerBaseBase
|
|
32
34
|
end
|
33
35
|
|
34
36
|
#stolen from Rspec
|
35
|
-
def close
|
37
|
+
def close(*args)
|
36
38
|
@output.close if (IO === @output) & (@output != $stdout)
|
37
39
|
end
|
38
40
|
|
41
|
+
protected
|
42
|
+
|
39
43
|
# do not let multiple processes get in each others way
|
40
44
|
def lock_output
|
41
45
|
if File === @output
|
@@ -3,6 +3,7 @@ require "parallel_tests/test/runner"
|
|
3
3
|
module ParallelTests
|
4
4
|
module RSpec
|
5
5
|
class Runner < ParallelTests::Test::Runner
|
6
|
+
DEV_NULL = (WINDOWS ? "NUL" : "/dev/null")
|
6
7
|
NAME = 'RSpec'
|
7
8
|
|
8
9
|
class << self
|
@@ -16,15 +17,15 @@ module ParallelTests
|
|
16
17
|
|
17
18
|
def determine_executable
|
18
19
|
cmd = case
|
19
|
-
when File.
|
20
|
-
"bin/rspec"
|
20
|
+
when File.exist?("bin/rspec")
|
21
|
+
WINDOWS ? "ruby bin/rspec" : "bin/rspec"
|
21
22
|
when File.file?("script/spec")
|
22
23
|
"script/spec"
|
23
24
|
when ParallelTests.bundler_enabled?
|
24
25
|
cmd = (run("bundle show rspec-core") =~ %r{Could not find gem.*} ? "spec" : "rspec")
|
25
26
|
"bundle exec #{cmd}"
|
26
27
|
else
|
27
|
-
%w[spec rspec].detect{|cmd| system "#{cmd} --version >
|
28
|
+
%w[spec rspec].detect{|cmd| system "#{cmd} --version > #{DEV_NULL} 2>&1" }
|
28
29
|
end
|
29
30
|
|
30
31
|
cmd or raise("Can't find executables rspec or spec")
|
@@ -39,7 +40,7 @@ module ParallelTests
|
|
39
40
|
end
|
40
41
|
|
41
42
|
def test_suffix
|
42
|
-
|
43
|
+
/_spec\.rb$/
|
43
44
|
end
|
44
45
|
|
45
46
|
private
|
@@ -5,7 +5,11 @@ class ParallelTests::RSpec::RuntimeLogger < ParallelTests::RSpec::LoggerBase
|
|
5
5
|
def initialize(*args)
|
6
6
|
super
|
7
7
|
@example_times = Hash.new(0)
|
8
|
-
@group_nesting = 0
|
8
|
+
@group_nesting = 0 unless RSPEC_1
|
9
|
+
end
|
10
|
+
|
11
|
+
if RSPEC_3
|
12
|
+
RSpec::Core::Formatters.register self, :example_group_started, :example_group_finished, :start_dump
|
9
13
|
end
|
10
14
|
|
11
15
|
if RSPEC_1
|
@@ -26,12 +30,13 @@ class ParallelTests::RSpec::RuntimeLogger < ParallelTests::RSpec::LoggerBase
|
|
26
30
|
super
|
27
31
|
end
|
28
32
|
|
29
|
-
def example_group_finished(
|
33
|
+
def example_group_finished(notification)
|
30
34
|
@group_nesting -= 1
|
31
35
|
if @group_nesting == 0
|
32
|
-
|
36
|
+
path = (RSPEC_3 ? notification.group.file_path : notification.file_path)
|
37
|
+
@example_times[path] += ParallelTests.now - @time
|
33
38
|
end
|
34
|
-
super
|
39
|
+
super if defined?(super)
|
35
40
|
end
|
36
41
|
end
|
37
42
|
|
@@ -45,7 +50,7 @@ class ParallelTests::RSpec::RuntimeLogger < ParallelTests::RSpec::LoggerBase
|
|
45
50
|
# TODO: Figure out why sometimes time can be less than 0
|
46
51
|
lock_output do
|
47
52
|
@example_times.each do |file, time|
|
48
|
-
relative_path = file.sub(/^#{Regexp.escape Dir.pwd}\//,'')
|
53
|
+
relative_path = file.sub(/^#{Regexp.escape Dir.pwd}\//,'').sub(/^\.\//, "")
|
49
54
|
@output.puts "#{relative_path}:#{time > 0 ? time : 0}"
|
50
55
|
end
|
51
56
|
end
|
@@ -1,19 +1,19 @@
|
|
1
1
|
require 'parallel_tests/rspec/failures_logger'
|
2
2
|
|
3
3
|
class ParallelTests::RSpec::SummaryLogger < ParallelTests::RSpec::LoggerBase
|
4
|
-
|
5
|
-
|
6
|
-
lock_output do
|
7
|
-
super
|
8
|
-
end
|
9
|
-
@output.flush
|
4
|
+
if RSPEC_3
|
5
|
+
RSpec::Core::Formatters.register self, :dump_failures
|
10
6
|
end
|
11
7
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
8
|
+
if RSPEC_1
|
9
|
+
def dump_failure(*args)
|
10
|
+
lock_output { super }
|
11
|
+
@output.flush
|
12
|
+
end
|
13
|
+
else
|
14
|
+
def dump_failures(*args)
|
15
|
+
lock_output { super }
|
16
|
+
@output.flush
|
16
17
|
end
|
17
|
-
@output.flush
|
18
18
|
end
|
19
19
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "parallel_tests/gherkin/runner"
|
2
|
+
|
3
|
+
module ParallelTests
|
4
|
+
module Spinach
|
5
|
+
class Runner < ParallelTests::Gherkin::Runner
|
6
|
+
class << self
|
7
|
+
def name
|
8
|
+
'spinach'
|
9
|
+
end
|
10
|
+
|
11
|
+
def runtime_logging
|
12
|
+
#Not Yet Supported
|
13
|
+
""
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/parallel_tests/tasks.rb
CHANGED
@@ -7,8 +7,12 @@ module ParallelTests
|
|
7
7
|
ENV['RAILS_ENV'] || 'test'
|
8
8
|
end
|
9
9
|
|
10
|
+
def purge_before_load
|
11
|
+
"db:test:purge" if Gem::Version.new(Rails.version) > Gem::Version.new('4.2.0')
|
12
|
+
end
|
13
|
+
|
10
14
|
def run_in_parallel(cmd, options={})
|
11
|
-
count = " -n #{options[:count]}"
|
15
|
+
count = " -n #{options[:count]}" unless options[:count].to_s.empty?
|
12
16
|
executable = File.expand_path("../../../bin/parallel_test", __FILE__)
|
13
17
|
command = "#{executable} --exec '#{cmd}'#{count}#{' --non-parallel' if options[:non_parallel]}"
|
14
18
|
command << " --advance-number #{options[:advance_number]}" if options[:advance_number]
|
@@ -31,17 +35,21 @@ module ParallelTests
|
|
31
35
|
activate_pipefail = "set -o pipefail"
|
32
36
|
remove_ignored_lines = %Q{(grep -v "#{ignore_regex}" || test 1)}
|
33
37
|
|
34
|
-
if system("#{activate_pipefail} 2>/dev/null && test 1")
|
35
|
-
|
38
|
+
if File.executable?('/bin/bash') && system('/bin/bash', '-c', "#{activate_pipefail} 2>/dev/null && test 1")
|
39
|
+
# We need to shell escape single quotes (' becomes '"'"') because
|
40
|
+
# run_in_parallel wraps command in single quotes
|
41
|
+
%Q{/bin/bash -c '"'"'#{activate_pipefail} && (#{command}) | #{remove_ignored_lines}'"'"'}
|
36
42
|
else
|
37
43
|
command
|
38
44
|
end
|
39
45
|
end
|
40
46
|
|
41
47
|
def check_for_pending_migrations
|
42
|
-
|
43
|
-
|
44
|
-
|
48
|
+
["db:abort_if_pending_migrations", "app:db:abort_if_pending_migrations"].each do |abort_migrations|
|
49
|
+
if Rake::Task.task_defined?(abort_migrations)
|
50
|
+
Rake::Task[abort_migrations].invoke
|
51
|
+
break
|
52
|
+
end
|
45
53
|
end
|
46
54
|
end
|
47
55
|
|
@@ -66,17 +74,17 @@ end
|
|
66
74
|
|
67
75
|
namespace :parallel do
|
68
76
|
desc "create test databases via db:create --> parallel:create[num_cpus]"
|
69
|
-
task :create, :count do |
|
77
|
+
task :create, :count do |_,args|
|
70
78
|
ParallelTests::Tasks.run_in_parallel("rake db:create RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
|
71
79
|
end
|
72
80
|
|
73
81
|
desc "drop test databases via db:drop --> parallel:drop[num_cpus]"
|
74
|
-
task :drop, :count do |
|
82
|
+
task :drop, :count do |_,args|
|
75
83
|
ParallelTests::Tasks.run_in_parallel("rake db:drop RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
|
76
84
|
end
|
77
85
|
|
78
86
|
desc "update test databases by dumping and loading --> parallel:prepare[num_cpus]"
|
79
|
-
task(:prepare, [:count]) do |
|
87
|
+
task(:prepare, [:count]) do |_,args|
|
80
88
|
ParallelTests::Tasks.check_for_pending_migrations
|
81
89
|
if defined?(ActiveRecord) && ActiveRecord::Base.schema_format == :ruby
|
82
90
|
# dump then load in parallel
|
@@ -85,34 +93,41 @@ namespace :parallel do
|
|
85
93
|
else
|
86
94
|
# there is no separate dump / load for schema_format :sql -> do it safe and slow
|
87
95
|
args = args.to_hash.merge(:non_parallel => true) # normal merge returns nil
|
88
|
-
|
96
|
+
taskname = Rake::Task.task_defined?('db:test:prepare') ? 'db:test:prepare' : 'app:db:test:prepare'
|
97
|
+
ParallelTests::Tasks.run_in_parallel("rake #{taskname}", args)
|
89
98
|
end
|
90
99
|
end
|
91
100
|
|
92
101
|
# when dumping/resetting takes too long
|
93
102
|
desc "update test databases via db:migrate --> parallel:migrate[num_cpus]"
|
94
|
-
task :migrate, :count do |
|
103
|
+
task :migrate, :count do |_,args|
|
95
104
|
ParallelTests::Tasks.run_in_parallel("rake db:migrate RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
|
96
105
|
end
|
97
106
|
|
98
107
|
# just load the schema (good for integration server <-> no development db)
|
99
108
|
desc "load dumped schema for test databases via db:schema:load --> parallel:load_schema[num_cpus]"
|
100
|
-
task :load_schema, :count do |
|
101
|
-
command = "rake db:schema:load RAILS_ENV=#{ParallelTests::Tasks.rails_env}"
|
109
|
+
task :load_schema, :count do |_,args|
|
110
|
+
command = "rake #{ParallelTests::Tasks.purge_before_load} db:schema:load RAILS_ENV=#{ParallelTests::Tasks.rails_env}"
|
102
111
|
ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_output(command, "^ ->\\|^-- "), args)
|
103
112
|
end
|
104
113
|
|
114
|
+
# load the structure from the structure.sql file
|
115
|
+
desc "load structure for test databases via db:structure:load --> parallel:load_structure[num_cpus]"
|
116
|
+
task :load_structure, :count do |_,args|
|
117
|
+
ParallelTests::Tasks.run_in_parallel("rake #{ParallelTests::Tasks.purge_before_load} db:structure:load RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
|
118
|
+
end
|
119
|
+
|
105
120
|
desc "load the seed data from db/seeds.rb via db:seed --> parallel:seed[num_cpus]"
|
106
|
-
task :seed, :count do |
|
121
|
+
task :seed, :count do |_,args|
|
107
122
|
ParallelTests::Tasks.run_in_parallel("rake db:seed RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
|
108
123
|
end
|
109
124
|
|
110
125
|
desc "launch given rake command in parallel"
|
111
|
-
task :rake, :command do |
|
126
|
+
task :rake, :command do |_, args|
|
112
127
|
ParallelTests::Tasks.run_in_parallel("RAILS_ENV=#{ParallelTests::Tasks.rails_env} rake #{args.command}")
|
113
128
|
end
|
114
129
|
|
115
|
-
['test', 'spec', 'features'].each do |type|
|
130
|
+
['test', 'spec', 'features', 'features-spinach'].each do |type|
|
116
131
|
desc "run #{type} in parallel with parallel:#{type}[num_cpus]"
|
117
132
|
task type, [:count, :pattern, :options] do |t, args|
|
118
133
|
ParallelTests::Tasks.check_for_pending_migrations
|
@@ -124,15 +139,23 @@ namespace :parallel do
|
|
124
139
|
test_framework = {
|
125
140
|
'spec' => 'rspec',
|
126
141
|
'test' => 'test',
|
127
|
-
'features' => 'cucumber'
|
142
|
+
'features' => 'cucumber',
|
143
|
+
'features-spinach' => 'spinach',
|
128
144
|
}[type]
|
129
145
|
|
146
|
+
if test_framework == 'spinach'
|
147
|
+
type = 'features'
|
148
|
+
end
|
130
149
|
executable = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'parallel_test')
|
150
|
+
|
131
151
|
command = "#{executable} #{type} --type #{test_framework} " \
|
132
152
|
"-n #{count} " \
|
133
153
|
"--pattern '#{pattern}' " \
|
134
154
|
"--test-options '#{options}'"
|
135
|
-
|
155
|
+
if ParallelTests::WINDOWS
|
156
|
+
ruby_binary = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name'])
|
157
|
+
command = "#{ruby_binary} #{command}"
|
158
|
+
end
|
136
159
|
abort unless system(command) # allow to chain tasks e.g. rake parallel:spec parallel:features
|
137
160
|
end
|
138
161
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'parallel_tests'
|
2
2
|
|
3
3
|
module ParallelTests
|
4
4
|
module Test
|
@@ -17,7 +17,7 @@ module ParallelTests
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def test_suffix
|
20
|
-
|
20
|
+
/_(test|spec).rb$/
|
21
21
|
end
|
22
22
|
|
23
23
|
def test_file_name
|
@@ -25,12 +25,13 @@ module ParallelTests
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def run_tests(test_files, process_number, num_processes, options)
|
28
|
-
require_list = test_files.map { |
|
29
|
-
cmd = "#{executable} -Itest -e '[#{require_list}].each {|f| require f }' -- #{options[:test_options]}"
|
28
|
+
require_list = test_files.map { |file| file.sub(" ", "\\ ") }.join(" ")
|
29
|
+
cmd = "#{executable} -Itest -e '%w[#{require_list}].each { |f| require %{./\#{f}} }' -- #{options[:test_options]}"
|
30
30
|
execute_command(cmd, process_number, num_processes, options)
|
31
31
|
end
|
32
32
|
|
33
33
|
def line_is_result?(line)
|
34
|
+
line.gsub!(/[.F*]/,'')
|
34
35
|
line =~ /\d+ failure/
|
35
36
|
end
|
36
37
|
|
@@ -38,31 +39,59 @@ module ParallelTests
|
|
38
39
|
|
39
40
|
# finds all tests and partitions them into groups
|
40
41
|
def tests_in_groups(tests, num_groups, options={})
|
42
|
+
tests = tests_with_size(tests, options)
|
43
|
+
Grouper.in_even_groups_by_size(tests, num_groups, options)
|
44
|
+
end
|
45
|
+
|
46
|
+
def tests_with_size(tests, options)
|
41
47
|
tests = find_tests(tests, options)
|
42
48
|
|
43
|
-
|
44
|
-
|
49
|
+
case options[:group_by]
|
50
|
+
when :found
|
51
|
+
tests.map! { |t| [t, 1] }
|
52
|
+
when :filesize
|
53
|
+
sort_by_filesize(tests)
|
54
|
+
when :runtime
|
55
|
+
sort_by_runtime(tests, runtimes(tests, options), options.merge(allowed_missing: 0.5))
|
56
|
+
when nil
|
57
|
+
# use recorded test runtime if we got enough data
|
58
|
+
runtimes = runtimes(tests, options) rescue []
|
59
|
+
if runtimes.size * 1.5 > tests.size
|
60
|
+
puts "Using recorded test runtime"
|
61
|
+
sort_by_runtime(tests, runtimes)
|
62
|
+
else
|
63
|
+
sort_by_filesize(tests)
|
64
|
+
end
|
45
65
|
else
|
46
|
-
|
66
|
+
raise ArgumentError, "Unsupported option #{options[:group_by]}"
|
47
67
|
end
|
48
|
-
|
68
|
+
|
69
|
+
tests
|
49
70
|
end
|
50
71
|
|
51
|
-
def execute_command(cmd, process_number,
|
72
|
+
def execute_command(cmd, process_number, num_processes, options)
|
52
73
|
env = (options[:env] || {}).merge(
|
53
74
|
"TEST_ENV_NUMBER" => test_env_number(process_number, options),
|
54
75
|
"PARALLEL_TEST_GROUPS" => num_processes
|
55
76
|
)
|
56
77
|
cmd = "nice #{cmd}" if options[:nice]
|
78
|
+
cmd = "#{cmd} 2>&1" if options[:combine_stderr]
|
79
|
+
puts cmd if options[:verbose]
|
80
|
+
|
57
81
|
execute_command_and_capture_output(env, cmd, options[:serialize_stdout])
|
58
82
|
end
|
59
83
|
|
60
84
|
def execute_command_and_capture_output(env, cmd, silence)
|
61
85
|
# make processes descriptive / visible in ps -ef
|
86
|
+
separator = (WINDOWS ? ' & ' : ';')
|
62
87
|
exports = env.map do |k,v|
|
63
|
-
|
64
|
-
|
65
|
-
|
88
|
+
if WINDOWS
|
89
|
+
"(SET \"#{k}=#{v}\")"
|
90
|
+
else
|
91
|
+
"#{k}=#{v};export #{k}"
|
92
|
+
end
|
93
|
+
end.join(separator)
|
94
|
+
cmd = "#{exports}#{separator}#{cmd}"
|
66
95
|
|
67
96
|
output = open("|#{cmd}", "r") { |output| capture_output(output, silence) }
|
68
97
|
exitstatus = $?.exitstatus
|
@@ -72,7 +101,7 @@ module ParallelTests
|
|
72
101
|
|
73
102
|
def find_results(test_output)
|
74
103
|
test_output.split("\n").map {|line|
|
75
|
-
line
|
104
|
+
line.gsub!(/\e\[\d+m/,'')
|
76
105
|
next unless line_is_result?(line)
|
77
106
|
line
|
78
107
|
}.compact
|
@@ -113,6 +142,9 @@ module ParallelTests
|
|
113
142
|
loop do
|
114
143
|
begin
|
115
144
|
read = out.readpartial(1000000) # read whatever chunk we can get
|
145
|
+
if Encoding.default_internal
|
146
|
+
read = read.force_encoding(Encoding.default_internal)
|
147
|
+
end
|
116
148
|
result << read
|
117
149
|
unless silence
|
118
150
|
$stdout.print read
|
@@ -123,29 +155,49 @@ module ParallelTests
|
|
123
155
|
result
|
124
156
|
end
|
125
157
|
|
126
|
-
def
|
127
|
-
|
158
|
+
def sort_by_runtime(tests, runtimes, options={})
|
159
|
+
allowed_missing = options[:allowed_missing] || 1.0
|
160
|
+
allowed_missing = tests.size * allowed_missing
|
128
161
|
|
129
|
-
#
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
162
|
+
# set know runtime for each test
|
163
|
+
tests.sort!
|
164
|
+
tests.map! do |test|
|
165
|
+
allowed_missing -= 1 unless time = runtimes[test]
|
166
|
+
raise "Too little runtime info" if allowed_missing < 0
|
167
|
+
[test, time]
|
168
|
+
end
|
169
|
+
|
170
|
+
if options[:verbose]
|
171
|
+
puts "Runtime found for #{tests.count(&:last)} of #{tests.size} tests"
|
172
|
+
end
|
173
|
+
|
174
|
+
# fill gaps with unknown-runtime if given, average otherwise
|
175
|
+
known, unknown = tests.partition(&:last)
|
176
|
+
average = (known.any? ? known.map!(&:last).inject(:+) / known.size : 1)
|
177
|
+
unknown_runtime = options[:unknown_runtime] || average
|
178
|
+
unknown.each { |set| set[1] = unknown_runtime }
|
179
|
+
end
|
180
|
+
|
181
|
+
def runtimes(tests, options)
|
182
|
+
log = options[:runtime_log] || runtime_log
|
183
|
+
lines = File.read(log).split("\n")
|
184
|
+
lines.each_with_object({}) do |line, times|
|
185
|
+
test, time = line.split(":", 2)
|
186
|
+
next unless test and time
|
187
|
+
times[test] = time.to_f if tests.include?(test)
|
141
188
|
end
|
142
189
|
end
|
143
190
|
|
191
|
+
def sort_by_filesize(tests)
|
192
|
+
tests.sort!
|
193
|
+
tests.map! { |test| [test, File.stat(test).size] }
|
194
|
+
end
|
195
|
+
|
144
196
|
def find_tests(tests, options = {})
|
145
197
|
(tests || []).map do |file_or_folder|
|
146
198
|
if File.directory?(file_or_folder)
|
147
199
|
files = files_in_folder(file_or_folder, options)
|
148
|
-
files.grep(
|
200
|
+
files.grep(options[:suffix]||test_suffix).grep(options[:pattern]||//)
|
149
201
|
else
|
150
202
|
file_or_folder
|
151
203
|
end
|