parallel_cucumber 0.1.22
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/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:
|