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,82 @@
|
|
|
1
|
+
require 'gherkin'
|
|
2
|
+
|
|
3
|
+
module ParallelTests
|
|
4
|
+
module Cucumber
|
|
5
|
+
class GherkinListener
|
|
6
|
+
attr_reader :collect
|
|
7
|
+
|
|
8
|
+
attr_writer :ignore_tag_pattern
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@steps, @uris = [], []
|
|
12
|
+
@collect = {}
|
|
13
|
+
reset_counters!
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def feature(feature)
|
|
17
|
+
@feature = feature
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def background(*args)
|
|
21
|
+
@background = 1
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def scenario(scenario)
|
|
25
|
+
@outline = @background = 0
|
|
26
|
+
return if should_ignore(scenario)
|
|
27
|
+
@scenarios += 1
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def scenario_outline(outline)
|
|
31
|
+
return if should_ignore(outline)
|
|
32
|
+
@outline = 1
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def step(*args)
|
|
36
|
+
return if @ignoring
|
|
37
|
+
if @background == 1
|
|
38
|
+
@background_steps += 1
|
|
39
|
+
elsif @outline > 0
|
|
40
|
+
@outline_steps += 1
|
|
41
|
+
else
|
|
42
|
+
@collect[@uri] += 1
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def uri(path)
|
|
47
|
+
@uri = path
|
|
48
|
+
@collect[@uri] = 0
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def examples(*args)
|
|
52
|
+
@examples += 1
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def eof(*args)
|
|
56
|
+
@collect[@uri] += (@background_steps * @scenarios) + (@outline_steps * @examples)
|
|
57
|
+
reset_counters!
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def reset_counters!
|
|
61
|
+
@examples = @outline = @outline_steps = @background = @background_steps = @scenarios = 0
|
|
62
|
+
@ignoring = nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# ignore lots of other possible callbacks ...
|
|
66
|
+
def method_missing(*args)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
# Return a combination of tags declared on this scenario/outline and the feature it belongs to
|
|
72
|
+
def all_tags(scenario)
|
|
73
|
+
(scenario.tags || []) + ((@feature && @feature.tags) || [])
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Set @ignoring if we should ignore this scenario/outline based on its tags
|
|
77
|
+
def should_ignore(scenario)
|
|
78
|
+
@ignoring = @ignore_tag_pattern && all_tags(scenario).find{ |tag| @ignore_tag_pattern === tag.name }
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'parallel_tests'
|
|
2
|
+
|
|
3
|
+
module ParallelTests
|
|
4
|
+
module Cucumber
|
|
5
|
+
module Io
|
|
6
|
+
|
|
7
|
+
def prepare_io(path_or_io)
|
|
8
|
+
if path_or_io.respond_to?(:write)
|
|
9
|
+
path_or_io
|
|
10
|
+
else # its a path
|
|
11
|
+
File.open(path_or_io, 'w').close # clean out the file
|
|
12
|
+
file = File.open(path_or_io, 'a')
|
|
13
|
+
|
|
14
|
+
at_exit do
|
|
15
|
+
unless file.closed?
|
|
16
|
+
file.flush
|
|
17
|
+
file.close
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
file
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# do not let multiple processes get in each others way
|
|
26
|
+
def lock_output
|
|
27
|
+
if File === @io
|
|
28
|
+
begin
|
|
29
|
+
@io.flock File::LOCK_EX
|
|
30
|
+
yield
|
|
31
|
+
ensure
|
|
32
|
+
@io.flock File::LOCK_UN
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
yield
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
require "parallel_tests/test/runner"
|
|
2
|
+
require 'shellwords'
|
|
3
|
+
|
|
4
|
+
module ParallelTests
|
|
5
|
+
module Cucumber
|
|
6
|
+
class Runner < ParallelTests::Test::Runner
|
|
7
|
+
NAME = 'Cucumber'
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def run_tests(test_files, process_number, num_processes, options)
|
|
11
|
+
sanitized_test_files = test_files.map { |val| Shellwords.escape(val) }
|
|
12
|
+
options = options.merge(:env => {"AUTOTEST" => "1"}) if $stdout.tty? # display color when we are in a terminal
|
|
13
|
+
runtime_logging = " --format ParallelTests::Cucumber::RuntimeLogger --out #{runtime_log}"
|
|
14
|
+
cmd = [
|
|
15
|
+
executable,
|
|
16
|
+
(runtime_logging if File.directory?(File.dirname(runtime_log))),
|
|
17
|
+
cucumber_opts(options[:test_options]),
|
|
18
|
+
*sanitized_test_files
|
|
19
|
+
].compact.join(" ")
|
|
20
|
+
execute_command(cmd, process_number, num_processes, options)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def determine_executable
|
|
24
|
+
case
|
|
25
|
+
when File.exists?("bin/cucumber")
|
|
26
|
+
"bin/cucumber"
|
|
27
|
+
when ParallelTests.bundler_enabled?
|
|
28
|
+
"bundle exec cucumber"
|
|
29
|
+
when File.file?("script/cucumber")
|
|
30
|
+
"script/cucumber"
|
|
31
|
+
else
|
|
32
|
+
"cucumber"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def runtime_log
|
|
37
|
+
'tmp/parallel_runtime_cucumber.log'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_file_name
|
|
41
|
+
"feature"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_suffix
|
|
45
|
+
".feature"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def line_is_result?(line)
|
|
49
|
+
line =~ /^\d+ (steps?|scenarios?)/
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# cucumber has 2 result lines per test run, that cannot be added
|
|
53
|
+
# 1 scenario (1 failed)
|
|
54
|
+
# 1 step (1 failed)
|
|
55
|
+
def summarize_results(results)
|
|
56
|
+
sort_order = %w[scenario step failed undefined skipped pending passed]
|
|
57
|
+
|
|
58
|
+
%w[scenario step].map do |group|
|
|
59
|
+
group_results = results.grep /^\d+ #{group}/
|
|
60
|
+
next if group_results.empty?
|
|
61
|
+
|
|
62
|
+
sums = sum_up_results(group_results)
|
|
63
|
+
sums = sums.sort_by { |word, _| sort_order.index(word) || 999 }
|
|
64
|
+
sums.map! do |word, number|
|
|
65
|
+
plural = "s" if word == group and number != 1
|
|
66
|
+
"#{number} #{word}#{plural}"
|
|
67
|
+
end
|
|
68
|
+
"#{sums[0]} (#{sums[1..-1].join(", ")})"
|
|
69
|
+
end.compact.join("\n")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def cucumber_opts(given)
|
|
73
|
+
if given =~ /--profile/ or given =~ /(^|\s)-p /
|
|
74
|
+
given
|
|
75
|
+
else
|
|
76
|
+
[given, profile_from_config].compact.join(" ")
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def profile_from_config
|
|
81
|
+
# copied from https://github.com/cucumber/cucumber/blob/master/lib/cucumber/cli/profile_loader.rb#L85
|
|
82
|
+
config = Dir.glob('{,.config/,config/}cucumber{.yml,.yaml}').first
|
|
83
|
+
if config && File.read(config) =~ /^parallel:/
|
|
84
|
+
"--profile parallel"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def tests_in_groups(tests, num_groups, options={})
|
|
89
|
+
if options[:group_by] == :steps
|
|
90
|
+
Grouper.by_steps(find_tests(tests, options), num_groups, options)
|
|
91
|
+
else
|
|
92
|
+
super
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'parallel_tests/cucumber/io'
|
|
2
|
+
|
|
3
|
+
module ParallelTests
|
|
4
|
+
module Cucumber
|
|
5
|
+
class RuntimeLogger
|
|
6
|
+
include Io
|
|
7
|
+
|
|
8
|
+
def initialize(step_mother, path_or_io, options=nil)
|
|
9
|
+
@io = prepare_io(path_or_io)
|
|
10
|
+
@example_times = Hash.new(0)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def before_feature(_)
|
|
14
|
+
@start_at = ParallelTests.now.to_f
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def after_feature(feature)
|
|
18
|
+
@example_times[feature.file] += ParallelTests.now.to_f - @start_at
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def after_features(*args)
|
|
22
|
+
lock_output do
|
|
23
|
+
@io.puts @example_times.map { |file, time| "#{file}:#{time}" }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module ParallelTests
|
|
2
|
+
class Grouper
|
|
3
|
+
class << self
|
|
4
|
+
def by_steps(tests, num_groups, options)
|
|
5
|
+
features_with_steps = build_features_with_steps(tests, options)
|
|
6
|
+
in_even_groups_by_size(features_with_steps, num_groups)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def in_even_groups_by_size(items_with_sizes, num_groups, options = {})
|
|
10
|
+
groups = Array.new(num_groups) { {:items => [], :size => 0} }
|
|
11
|
+
|
|
12
|
+
# add all files that should run in a single process to one group
|
|
13
|
+
(options[:single_process] || []).each do |pattern|
|
|
14
|
+
matched, items_with_sizes = items_with_sizes.partition { |item, size| item =~ pattern }
|
|
15
|
+
matched.each { |item, size| add_to_group(groups.first, item, size) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
groups_to_fill = (options[:isolate] ? groups[1..-1] : groups)
|
|
19
|
+
|
|
20
|
+
# add all other files
|
|
21
|
+
largest_first(items_with_sizes).each do |item, size|
|
|
22
|
+
smallest = smallest_group(groups_to_fill)
|
|
23
|
+
add_to_group(smallest, item, size)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
groups.map!{|g| g[:items].sort }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def largest_first(files)
|
|
32
|
+
files.sort_by{|item, size| size }.reverse
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def smallest_group(groups)
|
|
36
|
+
groups.min_by{|g| g[:size] }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def add_to_group(group, item, size)
|
|
40
|
+
group[:items] << item
|
|
41
|
+
group[:size] += size
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def build_features_with_steps(tests, options)
|
|
45
|
+
require 'parallel_tests/cucumber/gherkin_listener'
|
|
46
|
+
listener = Cucumber::GherkinListener.new
|
|
47
|
+
listener.ignore_tag_pattern = Regexp.compile(options[:ignore_tag_pattern]) if options[:ignore_tag_pattern]
|
|
48
|
+
parser = Gherkin::Parser::Parser.new(listener, true, 'root')
|
|
49
|
+
tests.each{|file|
|
|
50
|
+
parser.parse(File.read(file), file, 0)
|
|
51
|
+
}
|
|
52
|
+
listener.collect.sort_by{|_,value| -value }
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require 'parallel_tests/rspec/logger_base'
|
|
2
|
+
require 'parallel_tests/rspec/runner'
|
|
3
|
+
|
|
4
|
+
class ParallelTests::RSpec::FailuresLogger < ParallelTests::RSpec::LoggerBase
|
|
5
|
+
# RSpec 1: does not keep track of failures, so we do
|
|
6
|
+
def example_failed(example, *args)
|
|
7
|
+
if RSPEC_1
|
|
8
|
+
@failed_examples ||= []
|
|
9
|
+
@failed_examples << example
|
|
10
|
+
else
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# RSpec 1: dumps 1 failed spec
|
|
16
|
+
def dump_failure(*args)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# RSpec 2: dumps all failed specs
|
|
20
|
+
def dump_failures(*args)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def dump_summary(*args)
|
|
24
|
+
lock_output do
|
|
25
|
+
if RSPEC_1
|
|
26
|
+
dump_commands_to_rerun_failed_examples_rspec_1
|
|
27
|
+
else
|
|
28
|
+
dump_commands_to_rerun_failed_examples
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
@output.flush
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def dump_commands_to_rerun_failed_examples_rspec_1
|
|
37
|
+
(@failed_examples||[]).each do |example|
|
|
38
|
+
file, line = example.location.to_s.split(':')
|
|
39
|
+
next unless file and line
|
|
40
|
+
file.gsub!(%r(^.*?/spec/), './spec/')
|
|
41
|
+
@output.puts "#{ParallelTests::RSpec::Runner.send(:executable)} #{file}:#{line} # #{example.description}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module ParallelTests
|
|
2
|
+
module RSpec
|
|
3
|
+
end
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
begin
|
|
7
|
+
require 'rspec/core/formatters/base_text_formatter'
|
|
8
|
+
base = RSpec::Core::Formatters::BaseTextFormatter
|
|
9
|
+
rescue LoadError
|
|
10
|
+
require 'spec/runner/formatter/base_text_formatter'
|
|
11
|
+
base = Spec::Runner::Formatter::BaseTextFormatter
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
ParallelTests::RSpec::LoggerBaseBase = base
|
|
15
|
+
|
|
16
|
+
class ParallelTests::RSpec::LoggerBase < ParallelTests::RSpec::LoggerBaseBase
|
|
17
|
+
RSPEC_1 = !defined?(RSpec::Core::Formatters::BaseTextFormatter) # do not test for Spec, this will trigger deprecation warning in rspec 2
|
|
18
|
+
|
|
19
|
+
def initialize(*args)
|
|
20
|
+
super
|
|
21
|
+
|
|
22
|
+
@output ||= args[1] || args[0] # rspec 1 has output as second argument
|
|
23
|
+
|
|
24
|
+
if String === @output # a path ?
|
|
25
|
+
FileUtils.mkdir_p(File.dirname(@output))
|
|
26
|
+
File.open(@output, 'w'){} # overwrite previous results
|
|
27
|
+
@output = File.open(@output, 'a')
|
|
28
|
+
elsif File === @output # close and restart in append mode
|
|
29
|
+
@output.close
|
|
30
|
+
@output = File.open(@output.path, 'a')
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
#stolen from Rspec
|
|
35
|
+
def close
|
|
36
|
+
@output.close if (IO === @output) & (@output != $stdout)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# do not let multiple processes get in each others way
|
|
40
|
+
def lock_output
|
|
41
|
+
if File === @output
|
|
42
|
+
begin
|
|
43
|
+
@output.flock File::LOCK_EX
|
|
44
|
+
yield
|
|
45
|
+
ensure
|
|
46
|
+
@output.flock File::LOCK_UN
|
|
47
|
+
end
|
|
48
|
+
else
|
|
49
|
+
yield
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require "parallel_tests/test/runner"
|
|
2
|
+
|
|
3
|
+
module ParallelTests
|
|
4
|
+
module RSpec
|
|
5
|
+
class Runner < ParallelTests::Test::Runner
|
|
6
|
+
NAME = 'RSpec'
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def run_tests(test_files, process_number, num_processes, options)
|
|
10
|
+
exe = executable # expensive, so we cache
|
|
11
|
+
version = (exe =~ /\brspec\b/ ? 2 : 1)
|
|
12
|
+
cmd = [exe, options[:test_options], (rspec_2_color if version == 2), spec_opts, *test_files].compact.join(" ")
|
|
13
|
+
options = options.merge(:env => rspec_1_color) if version == 1
|
|
14
|
+
execute_command(cmd, process_number, num_processes, options)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def determine_executable
|
|
18
|
+
cmd = case
|
|
19
|
+
when File.exists?("bin/rspec")
|
|
20
|
+
"bin/rspec"
|
|
21
|
+
when File.file?("script/spec")
|
|
22
|
+
"script/spec"
|
|
23
|
+
when ParallelTests.bundler_enabled?
|
|
24
|
+
cmd = (run("bundle show rspec-core") =~ %r{Could not find gem.*} ? "spec" : "rspec")
|
|
25
|
+
"bundle exec #{cmd}"
|
|
26
|
+
else
|
|
27
|
+
%w[spec rspec].detect{|cmd| system "#{cmd} --version > /dev/null 2>&1" }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
cmd or raise("Can't find executables rspec or spec")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def runtime_log
|
|
34
|
+
'tmp/parallel_runtime_rspec.log'
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_file_name
|
|
38
|
+
"spec"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_suffix
|
|
42
|
+
"_spec.rb"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
# so it can be stubbed....
|
|
48
|
+
def run(cmd)
|
|
49
|
+
`#{cmd}`
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def rspec_1_color
|
|
53
|
+
if $stdout.tty?
|
|
54
|
+
{'RSPEC_COLOR' => "1"}
|
|
55
|
+
else
|
|
56
|
+
{}
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def rspec_2_color
|
|
61
|
+
'--color --tty' if $stdout.tty?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def spec_opts
|
|
65
|
+
options_file = ['.rspec_parallel', 'spec/parallel_spec.opts', 'spec/spec.opts'].detect{|f| File.file?(f) }
|
|
66
|
+
return unless options_file
|
|
67
|
+
"-O #{options_file}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'parallel_tests'
|
|
2
|
+
require 'parallel_tests/rspec/logger_base'
|
|
3
|
+
|
|
4
|
+
class ParallelTests::RSpec::RuntimeLogger < ParallelTests::RSpec::LoggerBase
|
|
5
|
+
def initialize(*args)
|
|
6
|
+
super
|
|
7
|
+
@example_times = Hash.new(0)
|
|
8
|
+
@group_nesting = 0 if !RSPEC_1
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
if RSPEC_1
|
|
12
|
+
def example_started(*args)
|
|
13
|
+
@time = ParallelTests.now
|
|
14
|
+
super
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def example_passed(example)
|
|
18
|
+
file = example.location.split(':').first
|
|
19
|
+
@example_times[file] += ParallelTests.now - @time
|
|
20
|
+
super
|
|
21
|
+
end
|
|
22
|
+
else
|
|
23
|
+
def example_group_started(example_group)
|
|
24
|
+
@time = ParallelTests.now if @group_nesting == 0
|
|
25
|
+
@group_nesting += 1
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def example_group_finished(example_group)
|
|
30
|
+
@group_nesting -= 1
|
|
31
|
+
if @group_nesting == 0
|
|
32
|
+
@example_times[example_group.file_path] += ParallelTests.now - @time
|
|
33
|
+
end
|
|
34
|
+
super
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def dump_summary(*args);end
|
|
39
|
+
def dump_failures(*args);end
|
|
40
|
+
def dump_failure(*args);end
|
|
41
|
+
def dump_pending(*args);end
|
|
42
|
+
|
|
43
|
+
def start_dump(*args)
|
|
44
|
+
return unless ENV['TEST_ENV_NUMBER'] #only record when running in parallel
|
|
45
|
+
# TODO: Figure out why sometimes time can be less than 0
|
|
46
|
+
lock_output do
|
|
47
|
+
@example_times.each do |file, time|
|
|
48
|
+
relative_path = file.sub(/^#{Regexp.escape Dir.pwd}\//,'')
|
|
49
|
+
@output.puts "#{relative_path}:#{time > 0 ? time : 0}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
@output.flush
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'parallel_tests/rspec/failures_logger'
|
|
2
|
+
|
|
3
|
+
class ParallelTests::RSpec::SummaryLogger < ParallelTests::RSpec::LoggerBase
|
|
4
|
+
# RSpec 1: dumps 1 failed spec
|
|
5
|
+
def dump_failure(*args)
|
|
6
|
+
lock_output do
|
|
7
|
+
super
|
|
8
|
+
end
|
|
9
|
+
@output.flush
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# RSpec 2: dumps all failed specs
|
|
13
|
+
def dump_failures(*args)
|
|
14
|
+
lock_output do
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
@output.flush
|
|
18
|
+
end
|
|
19
|
+
end
|