parallel_cucumber 0.1.22
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +15 -0
- data/bin/parallel_cucumber +9 -0
- data/lib/parallel_cucumber.rb +53 -0
- data/lib/parallel_cucumber/cli.rb +79 -0
- data/lib/parallel_cucumber/grouper.rb +95 -0
- data/lib/parallel_cucumber/result_formatter.rb +72 -0
- data/lib/parallel_cucumber/runner.rb +121 -0
- data/lib/parallel_cucumber/version.rb +3 -0
- metadata +95 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0712cec043620fc781e38673adc438452b993e59
|
4
|
+
data.tar.gz: a8c396bf23c857e9f269e36ea24b018b304de8c4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2561568ca2d8397944b67171a49839832b422d16135c21b749ca27965706c99165358f520f79f4464724e58978598b07901cc95c298a1a207661fd5fdbc325aa
|
7
|
+
data.tar.gz: 024e7fe7d1c6427080e94a7f572b3b45c3c940224dea48b956085b5be876107408970004953116f00f92d9b884ab2e2f6920d00df81fe003f6a8ecbe181dd91d
|
data/README.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Parallel Cucumber
|
2
|
+
|
3
|
+
```
|
4
|
+
Usage: parallel_cucumber [options] [ [FILE|DIR|URL][:LINE[:LINE]*] ]
|
5
|
+
Example: parallel_cucumber -n 4 -o "-f pretty -f html -o report.html" examples/i18n/en/features
|
6
|
+
-n [PROCESSES] How many processes to use
|
7
|
+
-o "[OPTIONS]", Run cucumber with these options
|
8
|
+
--cucumber-options
|
9
|
+
-e, --env-variables [JSON] Set additional environment variables to processes
|
10
|
+
-s, --setup-script [SCRIPT] Execute SCRIPT before each process
|
11
|
+
-t, --teardown-script [SCRIPT] Execute SCRIPT after each process
|
12
|
+
--thread-delay [SECONDS] Delay before next thread starting
|
13
|
+
-v, --version Show version
|
14
|
+
-h, --help Show this
|
15
|
+
```
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'parallel'
|
2
|
+
|
3
|
+
require 'parallel_cucumber/cli'
|
4
|
+
require 'parallel_cucumber/grouper'
|
5
|
+
require 'parallel_cucumber/result_formatter'
|
6
|
+
require 'parallel_cucumber/runner'
|
7
|
+
require 'parallel_cucumber/version'
|
8
|
+
|
9
|
+
module ParallelCucumber
|
10
|
+
class << self
|
11
|
+
def run_tests_in_parallel(options)
|
12
|
+
number_of_processes = options[:n]
|
13
|
+
test_results = nil
|
14
|
+
|
15
|
+
report_time_taken do
|
16
|
+
groups = Grouper.feature_groups(options, number_of_processes)
|
17
|
+
threads = groups.size
|
18
|
+
completed = []
|
19
|
+
|
20
|
+
on_finish = lambda do |_item, index, _result|
|
21
|
+
completed.push(index)
|
22
|
+
remaining_threads = ((0...threads).to_a - completed).sort
|
23
|
+
puts "Thread #{index} has finished. Remaining(#{remaining_threads.count}): #{remaining_threads.join(', ')}"
|
24
|
+
end
|
25
|
+
|
26
|
+
test_results = Parallel.map_with_index(
|
27
|
+
groups,
|
28
|
+
in_threads: threads,
|
29
|
+
finish: on_finish
|
30
|
+
) do |group, index|
|
31
|
+
Runner.new(options).run_tests(index, group)
|
32
|
+
end
|
33
|
+
puts 'All threads are complete'
|
34
|
+
ResultFormatter.report_results(test_results)
|
35
|
+
end
|
36
|
+
exit(1) if any_test_failed?(test_results)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def any_test_failed?(test_results)
|
42
|
+
test_results.any? { |result| result[:exit_status] != 0 }
|
43
|
+
end
|
44
|
+
|
45
|
+
def report_time_taken
|
46
|
+
start = Time.now
|
47
|
+
yield
|
48
|
+
time_in_sec = Time.now - start
|
49
|
+
mm, ss = time_in_sec.divmod(60)
|
50
|
+
puts "\nTook #{mm} Minutes, #{ss.round(2)} Seconds"
|
51
|
+
end
|
52
|
+
end # class
|
53
|
+
end # ParallelCucumber
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
module ParallelCucumber
|
5
|
+
module Cli
|
6
|
+
class << self
|
7
|
+
DEFAULTS = {
|
8
|
+
env_variables: {},
|
9
|
+
thread_delay: 0,
|
10
|
+
cucumber_options: '',
|
11
|
+
n: 1
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
def run(argv)
|
15
|
+
options = parse_options!(argv)
|
16
|
+
|
17
|
+
ParallelCucumber.run_tests_in_parallel(options)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def parse_options!(argv)
|
23
|
+
options = DEFAULTS.dup
|
24
|
+
|
25
|
+
option_parser = OptionParser.new do |opts|
|
26
|
+
opts.banner = [
|
27
|
+
'Usage: parallel_cucumber [options] [ [FILE|DIR|URL][:LINE[:LINE]*] ]',
|
28
|
+
'Example: parallel_cucumber -n 4 -o "-f pretty -f html -o report.html" examples/i18n/en/features'
|
29
|
+
].join("\n")
|
30
|
+
opts.on('-n [PROCESSES]', Integer, 'How many processes to use') { |n| options[:n] = n }
|
31
|
+
opts.on('-o', '--cucumber-options "[OPTIONS]"', 'Run cucumber with these options') do |cucumber_options|
|
32
|
+
options[:cucumber_options] = cucumber_options
|
33
|
+
end
|
34
|
+
opts.on('-e', '--env-variables [JSON]', 'Set additional environment variables to processes') do |env_vars|
|
35
|
+
options[:env_variables] = begin
|
36
|
+
JSON.parse(env_vars)
|
37
|
+
rescue JSON::ParserError
|
38
|
+
puts 'Additional environment variables not in JSON format. And do not forget to escape quotes'
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
opts.on('-s', '--setup-script [SCRIPT]', 'Execute SCRIPT before each process') do |script|
|
43
|
+
check_script(script)
|
44
|
+
options[:setup_script] = File.expand_path(script)
|
45
|
+
end
|
46
|
+
opts.on('-t', '--teardown-script [SCRIPT]', 'Execute SCRIPT after each process') do |script|
|
47
|
+
check_script(script)
|
48
|
+
options[:teardown_script] = File.expand_path(script)
|
49
|
+
end
|
50
|
+
opts.on('--thread-delay [SECONDS]', Float, 'Delay before next thread starting') do |thread_delay|
|
51
|
+
options[:thread_delay] = thread_delay
|
52
|
+
end
|
53
|
+
opts.on('-v', '--version', 'Show version') do
|
54
|
+
puts ParallelCucumber::VERSION
|
55
|
+
exit 0
|
56
|
+
end
|
57
|
+
opts.on('-h', '--help', 'Show this') do
|
58
|
+
puts opts
|
59
|
+
exit 0
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
option_parser.parse!(argv)
|
64
|
+
options[:cucumber_args] = argv
|
65
|
+
|
66
|
+
options
|
67
|
+
rescue OptionParser::InvalidOption => e
|
68
|
+
puts "Unknown option #{e}"
|
69
|
+
puts option_parser.help
|
70
|
+
exit 1
|
71
|
+
end
|
72
|
+
|
73
|
+
def check_script(path)
|
74
|
+
fail("File '#{path}' does not exist") unless File.exist?(path)
|
75
|
+
fail("File '#{path}' is not executable") unless File.executable?(path)
|
76
|
+
end
|
77
|
+
end # class
|
78
|
+
end # Cli
|
79
|
+
end # ParallelCucumber
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'English'
|
2
|
+
require 'erb'
|
3
|
+
require 'json'
|
4
|
+
require 'open3'
|
5
|
+
require 'tempfile'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
module ParallelCucumber
|
9
|
+
class Grouper
|
10
|
+
class << self
|
11
|
+
def feature_groups(options, group_size)
|
12
|
+
scenario_groups(group_size, options)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def scenario_groups(group_size, options)
|
18
|
+
distribution_data = generate_dry_run_report(options)
|
19
|
+
all_runnable_scenarios = distribution_data.map do |feature|
|
20
|
+
next if feature['elements'].nil?
|
21
|
+
feature['elements'].map do |scenario|
|
22
|
+
"#{feature['uri']}:#{scenario['line']}" if ['Scenario', 'Scenario Outline'].include?(scenario['keyword'])
|
23
|
+
end
|
24
|
+
end.flatten.compact
|
25
|
+
|
26
|
+
group_creator(group_size, all_runnable_scenarios)
|
27
|
+
end
|
28
|
+
|
29
|
+
def generate_dry_run_report(options)
|
30
|
+
cucumber_options = options[:cucumber_options]
|
31
|
+
cucumber_options = expand_profiles(cucumber_options) unless cucumber_config_file.nil?
|
32
|
+
cucumber_options = cucumber_options.gsub(/(--format|-f|--out|-o)\s+[^\s]+/, '')
|
33
|
+
result = nil
|
34
|
+
|
35
|
+
Tempfile.open(%w(dry-run .json)) do |f|
|
36
|
+
dry_run_options = "--dry-run --format json --out #{f.path}"
|
37
|
+
|
38
|
+
cmd = "cucumber #{cucumber_options} #{dry_run_options} #{options[:cucumber_args].join(' ')}"
|
39
|
+
_stdout, stderr, status = Open3.capture3(cmd)
|
40
|
+
f.close
|
41
|
+
|
42
|
+
if status != 0
|
43
|
+
cmd = "bundle exec #{cmd}" if ENV['BUNDLE_BIN_PATH']
|
44
|
+
fail("Can't generate dry run report, command exited with #{status}:\n\t#{cmd}\n\t#{stderr}")
|
45
|
+
end
|
46
|
+
|
47
|
+
content = File.read(f.path)
|
48
|
+
|
49
|
+
result = begin
|
50
|
+
JSON.parse(content)
|
51
|
+
rescue JSON::ParserError
|
52
|
+
content = content.length > 1024 ? "#{content[0...1000]} ...[TRUNCATED]..." : content
|
53
|
+
raise("Can't parse JSON from dry run:\n#{content}")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
result
|
57
|
+
end
|
58
|
+
|
59
|
+
def cucumber_config_file
|
60
|
+
Dir.glob('{,.config/,config/}cucumber{.yml,.yaml}').first
|
61
|
+
end
|
62
|
+
|
63
|
+
def expand_profiles(cucumber_options)
|
64
|
+
config = YAML.load(ERB.new(File.read(cucumber_config_file)).result)
|
65
|
+
_expand_profiles(cucumber_options, config)
|
66
|
+
end
|
67
|
+
|
68
|
+
def _expand_profiles(options, config)
|
69
|
+
expand_next = false
|
70
|
+
options.split.map do |option|
|
71
|
+
case
|
72
|
+
when %w(-p --profile).include?(option)
|
73
|
+
expand_next = true
|
74
|
+
next
|
75
|
+
when expand_next
|
76
|
+
expand_next = false
|
77
|
+
_expand_profiles(config[option], config)
|
78
|
+
else
|
79
|
+
option
|
80
|
+
end
|
81
|
+
end.compact.join(' ')
|
82
|
+
end
|
83
|
+
|
84
|
+
def group_creator(groups_count, tasks)
|
85
|
+
groups = Array.new(groups_count) { [] }
|
86
|
+
|
87
|
+
tasks.each do |task|
|
88
|
+
group = groups.min_by(&:size)
|
89
|
+
group.push(task)
|
90
|
+
end
|
91
|
+
groups.reject(&:empty?).map(&:compact)
|
92
|
+
end
|
93
|
+
end # class
|
94
|
+
end # Grouper
|
95
|
+
end # ParallelCucumber
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module ParallelCucumber
|
2
|
+
class ResultFormatter
|
3
|
+
class << self
|
4
|
+
def report_results(test_results)
|
5
|
+
results = find_results(test_results.map { |result| result[:stdout] }.join(''))
|
6
|
+
puts ''
|
7
|
+
puts summarize_results(results)
|
8
|
+
end
|
9
|
+
|
10
|
+
def find_results(test_output)
|
11
|
+
test_output.split("\n").map do |line|
|
12
|
+
line.gsub!(/\e\[\d+m/, '')
|
13
|
+
next unless line_is_result?(line)
|
14
|
+
line
|
15
|
+
end.compact
|
16
|
+
end
|
17
|
+
|
18
|
+
def line_is_result?(line)
|
19
|
+
line =~ scenario_or_step_result_regex || line =~ failing_scenario_regex
|
20
|
+
end
|
21
|
+
|
22
|
+
def summarize_results(results)
|
23
|
+
output = ["\n\n************ FINAL SUMMARY ************"]
|
24
|
+
|
25
|
+
failing_scenarios = results.grep(failing_scenario_regex)
|
26
|
+
if failing_scenarios.any?
|
27
|
+
failing_scenarios.unshift('Failing Scenarios:')
|
28
|
+
output << failing_scenarios.join("\n")
|
29
|
+
end
|
30
|
+
|
31
|
+
output << summary(results)
|
32
|
+
|
33
|
+
output.join("\n\n")
|
34
|
+
end
|
35
|
+
|
36
|
+
def summary(results)
|
37
|
+
sort_order = %w(scenario step failed undefined skipped pending passed)
|
38
|
+
|
39
|
+
%w(scenario step).map do |group|
|
40
|
+
group_results = results.grep(/^\d+ #{group}/)
|
41
|
+
next if group_results.empty?
|
42
|
+
|
43
|
+
sums = sum_up_results(group_results)
|
44
|
+
sums = sums.sort_by { |word, _| sort_order.index(word) || 999 }
|
45
|
+
sums.map! do |word, number|
|
46
|
+
plural = 's' if word == group && number != 1
|
47
|
+
"#{number} #{word}#{plural}"
|
48
|
+
end
|
49
|
+
"#{sums[0]} (#{sums[1..-1].join(', ')})"
|
50
|
+
end.compact.join("\n")
|
51
|
+
end
|
52
|
+
|
53
|
+
def sum_up_results(results)
|
54
|
+
results = results.join(' ').gsub(/s\b/, '') # combine and singularize results
|
55
|
+
counts = results.scan(/(\d+) (\w+)/)
|
56
|
+
counts.each_with_object(Hash.new(0)) do |(number, word), sum|
|
57
|
+
sum[word] += number.to_i
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def scenario_or_step_result_regex
|
64
|
+
/^\d+ (steps?|scenarios?)/
|
65
|
+
end
|
66
|
+
|
67
|
+
def failing_scenario_regex
|
68
|
+
%r{^cucumber .*features/.+:\d+}
|
69
|
+
end
|
70
|
+
end # class
|
71
|
+
end # ResultFormatter
|
72
|
+
end # ParallelCucumber
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'English'
|
2
|
+
|
3
|
+
module ParallelCucumber
|
4
|
+
class Runner
|
5
|
+
def initialize(options)
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def run_tests(process_number, cucumber_args)
|
10
|
+
cmd = command_for_test(process_number, cucumber_args)
|
11
|
+
execute_command_for_process(process_number, cmd)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def command_for_test(process_number, cucumber_args)
|
17
|
+
thread_delay = @options[:thread_delay]
|
18
|
+
cucumber_options = @options[:cucumber_options]
|
19
|
+
setup_script = @options[:setup_script]
|
20
|
+
teardown_script = @options[:teardown_script]
|
21
|
+
|
22
|
+
delay_cmd = thread_delay > 0 ? "sleep #{thread_delay * process_number}" : nil
|
23
|
+
|
24
|
+
cucumber_cmd = ['cucumber', cucumber_options, *cucumber_args].compact.join(' ')
|
25
|
+
|
26
|
+
cmd = [delay_cmd, setup_script, cucumber_cmd].compact.join(' && ')
|
27
|
+
teardown_script.nil? ? cmd : "#{cmd}; RET=$?; #{teardown_script}; exit ${RET}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def execute_command_for_process(process_number, cmd)
|
31
|
+
env = env_for_process(process_number)
|
32
|
+
print_chevron_msg(process_number, "Custom env: #{env.map { |k, v| "#{k}=#{v}" }.join(' ')}; command: #{cmd}")
|
33
|
+
|
34
|
+
begin
|
35
|
+
output = IO.popen(env, "#{cmd} 2>&1") do |io|
|
36
|
+
print_chevron_msg(process_number, "Pid: #{io.pid}")
|
37
|
+
show_output(io, process_number)
|
38
|
+
end
|
39
|
+
ensure
|
40
|
+
exit_status = -1
|
41
|
+
if !$CHILD_STATUS.nil? && $CHILD_STATUS.exited?
|
42
|
+
exit_status = $CHILD_STATUS.exitstatus
|
43
|
+
print_chevron_msg(process_number, "Exited with status #{exit_status}")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
{ stdout: output, exit_status: exit_status }
|
48
|
+
end
|
49
|
+
|
50
|
+
def env_for_process(process_number)
|
51
|
+
env_variables = @options[:env_variables]
|
52
|
+
env = env_variables.map do |k, v|
|
53
|
+
case v
|
54
|
+
when String, Numeric, TrueClass, FalseClass
|
55
|
+
[k, v]
|
56
|
+
when Array
|
57
|
+
[k, v[process_number]]
|
58
|
+
when Hash
|
59
|
+
value = v[process_number.to_s]
|
60
|
+
[k, value] unless value.nil?
|
61
|
+
when NilClass
|
62
|
+
else
|
63
|
+
fail("Don't know how to set '#{v}'(#{v.class}) to the environment variable '#{k}'")
|
64
|
+
end
|
65
|
+
end.compact.to_h
|
66
|
+
|
67
|
+
{
|
68
|
+
TEST: 1,
|
69
|
+
TEST_PROCESS_NUMBER: process_number
|
70
|
+
}.merge(env).map { |k, v| [k.to_s, v.to_s] }.to_h
|
71
|
+
end
|
72
|
+
|
73
|
+
def print_chevron_msg(chevron, line, io = $stdout)
|
74
|
+
msg = "#{chevron}> #{line}\n"
|
75
|
+
io.print(msg)
|
76
|
+
io.flush
|
77
|
+
end
|
78
|
+
|
79
|
+
def show_output(io, process_number)
|
80
|
+
file = File.open("process_#{process_number}.log", 'w')
|
81
|
+
remaining_part = ''
|
82
|
+
probable_finish = false
|
83
|
+
begin
|
84
|
+
loop do
|
85
|
+
text_block = remaining_part + io.read_nonblock(32 * 1024)
|
86
|
+
lines = text_block.split("\n")
|
87
|
+
remaining_part = lines.pop
|
88
|
+
probable_finish = last_cucumber_line?(remaining_part)
|
89
|
+
lines.each do |line|
|
90
|
+
probable_finish = true if last_cucumber_line?(line)
|
91
|
+
print_chevron_msg(process_number, line)
|
92
|
+
file.write("#{line}\n")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
rescue IO::WaitReadable
|
96
|
+
timeout = probable_finish ? 10 : 1800
|
97
|
+
result = IO.select([io], [], [], timeout)
|
98
|
+
if result.nil?
|
99
|
+
if probable_finish
|
100
|
+
print_chevron_msg(process_number,
|
101
|
+
"Timeout reached in #{timeout}s, but process has probably finished", $stderr)
|
102
|
+
else
|
103
|
+
raise("Read timeout has reached for process #{process_number}. There is no output in #{timeout}s")
|
104
|
+
end
|
105
|
+
else
|
106
|
+
retry
|
107
|
+
end
|
108
|
+
rescue EOFError # rubocop:disable Lint/HandleExceptions
|
109
|
+
ensure
|
110
|
+
print_chevron_msg(process_number, remaining_part)
|
111
|
+
file.write(remaining_part.to_s)
|
112
|
+
file.close unless file.nil?
|
113
|
+
end
|
114
|
+
File.read("process_#{process_number}.log")
|
115
|
+
end
|
116
|
+
|
117
|
+
def last_cucumber_line?(line)
|
118
|
+
!(line =~ /\d+m[\d\.]+s/).nil?
|
119
|
+
end
|
120
|
+
end # Runner
|
121
|
+
end # ParallelCucumber
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: parallel_cucumber
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.22
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alexander Bayandin
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-01-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: cucumber
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: parallel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.36'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.36'
|
55
|
+
description:
|
56
|
+
email: a.bayandin@gmail.com
|
57
|
+
executables:
|
58
|
+
- parallel_cucumber
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- README.md
|
63
|
+
- bin/parallel_cucumber
|
64
|
+
- lib/parallel_cucumber.rb
|
65
|
+
- lib/parallel_cucumber/cli.rb
|
66
|
+
- lib/parallel_cucumber/grouper.rb
|
67
|
+
- lib/parallel_cucumber/result_formatter.rb
|
68
|
+
- lib/parallel_cucumber/runner.rb
|
69
|
+
- lib/parallel_cucumber/version.rb
|
70
|
+
homepage: https://github.com/bayandin/parallel_cucumber
|
71
|
+
licenses:
|
72
|
+
- MIT
|
73
|
+
metadata: {}
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
requirements: []
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 2.4.8
|
91
|
+
signing_key:
|
92
|
+
specification_version: 4
|
93
|
+
summary: Run cucumber in parallel
|
94
|
+
test_files: []
|
95
|
+
has_rdoc:
|