flatware 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/flatware/cli.rb +26 -14
- data/lib/flatware/version.rb +1 -1
- metadata +5 -26
- data/lib/flatware-cucumber.rb +0 -1
- data/lib/flatware-rspec.rb +0 -1
- data/lib/flatware/cucumber.rb +0 -50
- data/lib/flatware/cucumber/cli.rb +0 -22
- data/lib/flatware/cucumber/formatter.rb +0 -66
- data/lib/flatware/cucumber/formatters/console.rb +0 -48
- data/lib/flatware/cucumber/formatters/console/summary.rb +0 -65
- data/lib/flatware/cucumber/result.rb +0 -27
- data/lib/flatware/cucumber/runtime.rb +0 -36
- data/lib/flatware/cucumber/scenario_result.rb +0 -38
- data/lib/flatware/cucumber/step_result.rb +0 -30
- data/lib/flatware/rspec.rb +0 -28
- data/lib/flatware/rspec/checkpoint.rb +0 -29
- data/lib/flatware/rspec/cli.rb +0 -16
- data/lib/flatware/rspec/example_notification.rb +0 -21
- data/lib/flatware/rspec/examples_notification.rb +0 -24
- data/lib/flatware/rspec/formatter.rb +0 -50
- data/lib/flatware/rspec/formatters/console.rb +0 -33
- data/lib/flatware/rspec/job_builder.rb +0 -110
- data/lib/flatware/rspec/summary.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9da02a0ce1009b7c0fc4c03cd33af10e5e2984b706ef46aca34dd8f33c593267
|
4
|
+
data.tar.gz: 94563ca34708d9d7563f36291eb781ab6f48654a09a0fe7d7912d04aae008a5b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ac813bab61e13d630fde64a875e57eb973fd74c6a59d48772ab1ec2d5a688a99899b70e4874c9fcdd71348f88a4b668a97955fdea156c667968fe2ca63523fe
|
7
|
+
data.tar.gz: d7af5aa56a6ffd45043834440c144f8baebc7c7b59a6fc1d80e93dddc959d20f3db3b6cb66cd183e3f055898abbcd09ec9864266ed91f8df361efae15f70671e
|
data/lib/flatware/cli.rb
CHANGED
@@ -1,37 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'thor'
|
2
4
|
require 'flatware/pids'
|
3
5
|
module Flatware
|
4
6
|
class CLI < Thor
|
5
|
-
|
6
7
|
def self.processors
|
7
8
|
@processors ||= ProcessorInfo.count
|
8
9
|
end
|
9
10
|
|
10
11
|
def self.worker_option
|
11
|
-
method_option :workers, aliases:
|
12
|
+
method_option :workers, aliases: '-w', type: :numeric, default: processors, desc: 'Number of concurent processes to run'
|
12
13
|
end
|
13
14
|
|
14
|
-
class_option :log, aliases:
|
15
|
+
class_option :log, aliases: '-l', type: :boolean, desc: 'Print debug messages to $stderr'
|
15
16
|
|
16
17
|
worker_option
|
17
|
-
desc
|
18
|
+
desc 'fan [COMMAND]', 'executes the given job on all of the workers'
|
18
19
|
def fan(*command)
|
19
20
|
Flatware.verbose = options[:log]
|
20
21
|
|
21
|
-
command = command.join(
|
22
|
+
command = command.join(' ')
|
22
23
|
puts "Running '#{command}' on #{workers} workers"
|
23
24
|
|
24
25
|
workers.times do |i|
|
25
26
|
fork do
|
26
|
-
exec({
|
27
|
+
exec({ 'TEST_ENV_NUMBER' => i.to_s }, command)
|
27
28
|
end
|
28
29
|
end
|
29
30
|
Process.waitall
|
30
31
|
end
|
31
32
|
|
32
|
-
desc
|
33
|
+
desc 'clear', 'kills all flatware processes'
|
33
34
|
def clear
|
34
|
-
(Flatware.pids - [
|
35
|
+
(Flatware.pids - [$PROCESS_ID]).each do |pid|
|
35
36
|
Process.kill 6, pid
|
36
37
|
end
|
37
38
|
end
|
@@ -39,7 +40,7 @@ module Flatware
|
|
39
40
|
private
|
40
41
|
|
41
42
|
def start_sink(jobs:, workers:, formatter:)
|
42
|
-
|
43
|
+
$0 = 'flatware sink'
|
43
44
|
Process.setpgrp
|
44
45
|
passed = Sink.start_server jobs: jobs, formatter: Flatware::Broadcaster.new([formatter]), sink: options['sink-endpoint'], dispatch: options['dispatch-endpoint'], worker_count: workers
|
45
46
|
exit passed ? 0 : 1
|
@@ -55,10 +56,21 @@ module Flatware
|
|
55
56
|
end
|
56
57
|
end
|
57
58
|
|
58
|
-
flatware_gems =
|
59
|
+
flatware_gems = %w[flatware-rspec flatware-cucumber]
|
60
|
+
|
61
|
+
loaded_flatware_gem_count = flatware_gems.map do |flatware_gem|
|
62
|
+
begin
|
63
|
+
require flatware_gem
|
64
|
+
rescue LoadError
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
end.compact.size
|
68
|
+
|
69
|
+
if loaded_flatware_gem_count.zero?
|
70
|
+
warn(
|
71
|
+
format(<<~MESSAGE, gem_list: flatware_gems.join(' or ')))
|
72
|
+
The flatware gem is a dependency of flatware runners for rspec and cucumber.
|
73
|
+
Install %<gem_list>s for more usefull commands.
|
74
|
+
MESSAGE
|
59
75
|
|
60
|
-
if flatware_gems.count > 0
|
61
|
-
flatware_gems.each(&method(:require))
|
62
|
-
else
|
63
|
-
puts "The flatware gem is a dependency of flatware runners for rspec and cucumber. Try adding flatware-rspec or flatware-cucumber to your Gemfile."
|
64
76
|
end
|
data/lib/flatware/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flatware
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Dunn
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-08-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi-rzmq
|
@@ -66,7 +66,7 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: 10.1.0
|
69
|
-
description: A distributed
|
69
|
+
description: A distributed rspec and cucumber runner
|
70
70
|
email: brian@hashrocket.com
|
71
71
|
executables:
|
72
72
|
- flatware
|
@@ -78,33 +78,13 @@ files:
|
|
78
78
|
- LICENSE.txt
|
79
79
|
- README.md
|
80
80
|
- bin/flatware
|
81
|
-
- lib/flatware-cucumber.rb
|
82
|
-
- lib/flatware-rspec.rb
|
83
81
|
- lib/flatware.rb
|
84
82
|
- lib/flatware/broadcaster.rb
|
85
83
|
- lib/flatware/cli.rb
|
86
|
-
- lib/flatware/cucumber.rb
|
87
|
-
- lib/flatware/cucumber/cli.rb
|
88
|
-
- lib/flatware/cucumber/formatter.rb
|
89
|
-
- lib/flatware/cucumber/formatters/console.rb
|
90
|
-
- lib/flatware/cucumber/formatters/console/summary.rb
|
91
|
-
- lib/flatware/cucumber/result.rb
|
92
|
-
- lib/flatware/cucumber/runtime.rb
|
93
|
-
- lib/flatware/cucumber/scenario_result.rb
|
94
|
-
- lib/flatware/cucumber/step_result.rb
|
95
84
|
- lib/flatware/job.rb
|
96
85
|
- lib/flatware/pids.rb
|
97
86
|
- lib/flatware/poller.rb
|
98
87
|
- lib/flatware/processor_info.rb
|
99
|
-
- lib/flatware/rspec.rb
|
100
|
-
- lib/flatware/rspec/checkpoint.rb
|
101
|
-
- lib/flatware/rspec/cli.rb
|
102
|
-
- lib/flatware/rspec/example_notification.rb
|
103
|
-
- lib/flatware/rspec/examples_notification.rb
|
104
|
-
- lib/flatware/rspec/formatter.rb
|
105
|
-
- lib/flatware/rspec/formatters/console.rb
|
106
|
-
- lib/flatware/rspec/job_builder.rb
|
107
|
-
- lib/flatware/rspec/summary.rb
|
108
88
|
- lib/flatware/serialized_exception.rb
|
109
89
|
- lib/flatware/sink.rb
|
110
90
|
- lib/flatware/sink/client.rb
|
@@ -130,9 +110,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
110
|
- !ruby/object:Gem::Version
|
131
111
|
version: '0'
|
132
112
|
requirements: []
|
133
|
-
|
134
|
-
rubygems_version: 2.7.6
|
113
|
+
rubygems_version: 3.0.3
|
135
114
|
signing_key:
|
136
115
|
specification_version: 4
|
137
|
-
summary: A distributed
|
116
|
+
summary: A distributed rspec and cucumber runner
|
138
117
|
test_files: []
|
data/lib/flatware-cucumber.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require 'flatware/cucumber'
|
data/lib/flatware-rspec.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require 'flatware/rspec'
|
data/lib/flatware/cucumber.rb
DELETED
@@ -1,50 +0,0 @@
|
|
1
|
-
require 'cucumber'
|
2
|
-
require 'flatware/cucumber/formatter'
|
3
|
-
require 'flatware/cucumber/result'
|
4
|
-
require 'flatware/cucumber/scenario_result'
|
5
|
-
require 'flatware/cucumber/step_result'
|
6
|
-
require 'flatware/cucumber/formatters/console'
|
7
|
-
require 'flatware/cucumber/cli'
|
8
|
-
|
9
|
-
module Flatware
|
10
|
-
module Cucumber
|
11
|
-
class Config
|
12
|
-
attr_reader :config, :args
|
13
|
-
def initialize(cucumber_config, args)
|
14
|
-
@config, @args = cucumber_config, args
|
15
|
-
end
|
16
|
-
|
17
|
-
def feature_dir
|
18
|
-
@config.feature_dirs.first
|
19
|
-
end
|
20
|
-
|
21
|
-
def jobs
|
22
|
-
feature_files.map { |file| Job.new file, args }.to_a
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def feature_files
|
28
|
-
config.feature_files - config.feature_dirs
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
extend self
|
33
|
-
|
34
|
-
def configure(args, out_stream=$stdout, error_stream=$stderr)
|
35
|
-
raw_args = args.dup
|
36
|
-
cli_config = ::Cucumber::Cli::Configuration.new(out_stream, error_stream)
|
37
|
-
cli_config.parse! args + %w[--format Flatware::Cucumber::Formatter]
|
38
|
-
cucumber_config = ::Cucumber::Configuration.new cli_config
|
39
|
-
Config.new cucumber_config, raw_args
|
40
|
-
end
|
41
|
-
|
42
|
-
def run(feature_files, options)
|
43
|
-
runtime(Array(feature_files) + options).run!
|
44
|
-
end
|
45
|
-
|
46
|
-
def runtime(args)
|
47
|
-
::Cucumber::Runtime.new(configure(args).config)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
require 'flatware/cli'
|
2
|
-
|
3
|
-
module Flatware
|
4
|
-
class CLI
|
5
|
-
worker_option
|
6
|
-
method_option 'dispatch-endpoint', type: :string, default: 'ipc://dispatch'
|
7
|
-
method_option 'sink-endpoint', type: :string, default: 'ipc://task'
|
8
|
-
desc "cucumber [FLATWARE_OPTS] [CUCUMBER_ARGS]", "parallelizes cucumber with custom arguments"
|
9
|
-
def cucumber(*args)
|
10
|
-
config = Cucumber.configure args
|
11
|
-
|
12
|
-
unless config.jobs.any?
|
13
|
-
puts "Please create some feature files in the #{config.feature_dir} directory."
|
14
|
-
exit 1
|
15
|
-
end
|
16
|
-
|
17
|
-
Flatware.verbose = options[:log]
|
18
|
-
Worker.spawn count: workers, runner: Cucumber, dispatch: options['dispatch-endpoint'], sink: options['sink-endpoint']
|
19
|
-
start_sink jobs: config.jobs, workers: workers, formatter: Flatware::Cucumber::Formatters::Console.new($stdout, $stderr)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
@@ -1,66 +0,0 @@
|
|
1
|
-
require 'cucumber'
|
2
|
-
require 'flatware/sink'
|
3
|
-
require 'ostruct'
|
4
|
-
module Flatware
|
5
|
-
module Cucumber
|
6
|
-
class Formatter
|
7
|
-
Checkpoint = Struct.new :steps, :scenarios do
|
8
|
-
def failures?
|
9
|
-
scenarios.any? &:failed?
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
Scenario = Struct.new :name, :file_colon_line, :status do
|
14
|
-
def failed?
|
15
|
-
status == :failed
|
16
|
-
end
|
17
|
-
|
18
|
-
def failed_outside_step?
|
19
|
-
false
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def initialize(config)
|
24
|
-
config.on_event :test_case_finished, &method(:on_test_case_finished)
|
25
|
-
config.on_event :test_step_finished, &method(:on_test_step_finished)
|
26
|
-
config.on_event :test_run_finished, &method(:on_test_run_finished)
|
27
|
-
config.on_event :step_activated, &method(:on_step_activated)
|
28
|
-
reset
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
def reset
|
34
|
-
@steps = []
|
35
|
-
@scenarios = []
|
36
|
-
@matched_steps = Set.new
|
37
|
-
end
|
38
|
-
|
39
|
-
attr_reader :steps, :scenarios, :matched_steps
|
40
|
-
|
41
|
-
def on_test_case_finished(event)
|
42
|
-
scenarios << Scenario.new(event.test_case.name, event.test_case.location.to_s, event.result.to_sym)
|
43
|
-
end
|
44
|
-
|
45
|
-
def on_step_activated(event)
|
46
|
-
@matched_steps << event.step_match.location.to_s
|
47
|
-
end
|
48
|
-
|
49
|
-
def on_test_step_finished(event)
|
50
|
-
if really_a_step?(event.test_step) or event.result.undefined?
|
51
|
-
steps << StepResult.new(event.result.to_sym, event.result.failed? && event.result.exception)
|
52
|
-
Sink::client.progress Result.new event.result.to_sym
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def on_test_run_finished(*)
|
57
|
-
Sink::client.checkpoint Checkpoint.new steps, scenarios
|
58
|
-
reset
|
59
|
-
end
|
60
|
-
|
61
|
-
def really_a_step?(step)
|
62
|
-
matched_steps.include?(step.action_location.to_s)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
@@ -1,48 +0,0 @@
|
|
1
|
-
require 'flatware/cucumber/formatters/console/summary'
|
2
|
-
require 'cucumber/formatter/console'
|
3
|
-
|
4
|
-
module Flatware::Cucumber::Formatters
|
5
|
-
class Console
|
6
|
-
#for format_string
|
7
|
-
include ::Cucumber::Formatter::Console
|
8
|
-
|
9
|
-
FORMATS = {
|
10
|
-
passed: '.',
|
11
|
-
failed: 'F',
|
12
|
-
undefined: 'U',
|
13
|
-
pending: 'P',
|
14
|
-
skipped: '-'
|
15
|
-
}
|
16
|
-
|
17
|
-
STATUSES = FORMATS.keys
|
18
|
-
|
19
|
-
attr_reader :out, :err
|
20
|
-
|
21
|
-
def initialize(stdout, stderr)
|
22
|
-
@out, @err = stdout, stderr
|
23
|
-
end
|
24
|
-
|
25
|
-
def progress(result)
|
26
|
-
out.print format result.progress
|
27
|
-
end
|
28
|
-
|
29
|
-
def summarize(checkpoints)
|
30
|
-
steps = checkpoints.flat_map(&:steps)
|
31
|
-
scenarios = checkpoints.flat_map(&:scenarios)
|
32
|
-
Summary.new(steps, scenarios, out).summarize
|
33
|
-
end
|
34
|
-
|
35
|
-
def summarize_remaining(remaining_jobs)
|
36
|
-
out.puts
|
37
|
-
out.puts "The following features have not been run:"
|
38
|
-
for job in remaining_jobs
|
39
|
-
out.puts job.id
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
def format(status)
|
45
|
-
format_string FORMATS[status], status
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
@@ -1,65 +0,0 @@
|
|
1
|
-
require 'cucumber/formatter/console'
|
2
|
-
require 'flatware/cucumber/formatters/console'
|
3
|
-
|
4
|
-
module Flatware::Cucumber::Formatters
|
5
|
-
class Console
|
6
|
-
class Summary
|
7
|
-
include ::Cucumber::Formatter::Console
|
8
|
-
attr_reader :io, :steps, :scenarios
|
9
|
-
|
10
|
-
def initialize(steps, scenarios=[], io=StringIO.new)
|
11
|
-
@io = io
|
12
|
-
@steps = steps
|
13
|
-
@scenarios = scenarios
|
14
|
-
end
|
15
|
-
|
16
|
-
def summarize
|
17
|
-
2.times { io.puts }
|
18
|
-
print_failures(steps, 'step')
|
19
|
-
print_failures(scenarios.select(&:failed_outside_step?), 'scenario')
|
20
|
-
print_failed_scenarios scenarios
|
21
|
-
print_counts 'scenario', scenarios
|
22
|
-
print_counts 'step', steps
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def print_failed_scenarios(scenarios)
|
28
|
-
return unless scenarios.any? &with_status(:failed)
|
29
|
-
|
30
|
-
io.puts format_string "Failing Scenarios:", :failed
|
31
|
-
scenarios.select(&with_status(:failed)).sort_by(&:file_colon_line).each do |scenario|
|
32
|
-
io.puts format_string(scenario.file_colon_line, :failed) + format_string(" # Scenario: " + scenario.name, :comment)
|
33
|
-
end
|
34
|
-
io.puts
|
35
|
-
end
|
36
|
-
|
37
|
-
def print_failures(collection, label)
|
38
|
-
failures = collection.select(&with_status(:failed))
|
39
|
-
print_elements failures, :failed, pluralize(label, failures.size)
|
40
|
-
end
|
41
|
-
|
42
|
-
def print_counts(label, collection)
|
43
|
-
io.puts pluralize(label, collection.size) + count_summary(collection)
|
44
|
-
end
|
45
|
-
|
46
|
-
def pluralize(word, number)
|
47
|
-
"#{number} #{number == 1 ? word : word + 's'}"
|
48
|
-
end
|
49
|
-
|
50
|
-
def with_status(status)
|
51
|
-
proc {|r| r.status == status}
|
52
|
-
end
|
53
|
-
|
54
|
-
def count_summary(results)
|
55
|
-
return "" unless results.any?
|
56
|
-
status_counts = STATUSES.map do |status|
|
57
|
-
count = results.select(&with_status(status)).size
|
58
|
-
format_string "#{count} #{status}", status if count > 0
|
59
|
-
end.compact.join ", "
|
60
|
-
|
61
|
-
" (#{status_counts})"
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
module Flatware
|
2
|
-
module Cucumber
|
3
|
-
class Result
|
4
|
-
attr_reader :progress, :worker
|
5
|
-
|
6
|
-
def initialize(progress)
|
7
|
-
@progress = progress
|
8
|
-
@worker = ENV['TEST_ENV_NUMBER'].to_i
|
9
|
-
end
|
10
|
-
|
11
|
-
class << self
|
12
|
-
def step(*args)
|
13
|
-
step = StepResult.new *args
|
14
|
-
new step.progress, [step]
|
15
|
-
end
|
16
|
-
|
17
|
-
def status(status)
|
18
|
-
new status
|
19
|
-
end
|
20
|
-
|
21
|
-
def background(status, exception)
|
22
|
-
new '', [StepResult.new(status, exception)]
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
@@ -1,36 +0,0 @@
|
|
1
|
-
require 'cucumber'
|
2
|
-
|
3
|
-
module Flatware
|
4
|
-
module Cucumber
|
5
|
-
class Runtime < ::Cucumber::Runtime
|
6
|
-
|
7
|
-
attr_accessor :configuration, :loader
|
8
|
-
attr_reader :out, :err
|
9
|
-
attr_reader :visitor
|
10
|
-
|
11
|
-
def initialize(out=StringIO.new, err=out)
|
12
|
-
@out, @err = out, err
|
13
|
-
super(default_configuration)
|
14
|
-
load_step_definitions
|
15
|
-
@results = Results.new(configuration)
|
16
|
-
end
|
17
|
-
|
18
|
-
def default_configuration
|
19
|
-
config = ::Cucumber::Cli::Configuration.new
|
20
|
-
config.parse! []
|
21
|
-
config
|
22
|
-
end
|
23
|
-
|
24
|
-
def run(feature_files=[], options=[])
|
25
|
-
@loader = nil
|
26
|
-
options = Array(feature_files) + %w[--format Flatware::Cucumber::Formatter] + options
|
27
|
-
|
28
|
-
configure(::Cucumber::Cli::Main.new(options, out, err).configuration)
|
29
|
-
|
30
|
-
self.visitor = configuration.build_tree_walker(self)
|
31
|
-
visitor.visit_features(features)
|
32
|
-
results
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
require 'flatware/serialized_exception'
|
2
|
-
module Flatware
|
3
|
-
module Cucumber
|
4
|
-
class ScenarioResult
|
5
|
-
attr_reader :status, :file_colon_line, :name
|
6
|
-
def initialize(status, file_colon_line, name, e)
|
7
|
-
@status = status
|
8
|
-
@file_colon_line = file_colon_line
|
9
|
-
@name = name
|
10
|
-
@exception = SerializedException.new(e.class, e.message, e.backtrace) if e
|
11
|
-
@failed_outside_step = false
|
12
|
-
end
|
13
|
-
|
14
|
-
def passed?
|
15
|
-
status == :passed
|
16
|
-
end
|
17
|
-
|
18
|
-
def failed?
|
19
|
-
status == :failed
|
20
|
-
end
|
21
|
-
|
22
|
-
def failed_outside_step!(file_colon_line)
|
23
|
-
@failed_outside_step = file_colon_line
|
24
|
-
end
|
25
|
-
|
26
|
-
def failed_outside_step?
|
27
|
-
!!@failed_outside_step
|
28
|
-
end
|
29
|
-
|
30
|
-
def exception
|
31
|
-
@exception.tap do |e|
|
32
|
-
e.backtrace = e.backtrace.grep(Regexp.new(Dir.pwd)).map { |line| line[Dir.pwd.size..-1] }
|
33
|
-
e.backtrace = e.backtrace + [@failed_outside_step] if failed_outside_step?
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
require 'flatware/serialized_exception'
|
2
|
-
module Flatware
|
3
|
-
module Cucumber
|
4
|
-
class StepResult
|
5
|
-
attr_reader :status, :exception
|
6
|
-
|
7
|
-
def initialize(status, exception)
|
8
|
-
@status, @exception = status, (serialized(exception) if exception)
|
9
|
-
end
|
10
|
-
|
11
|
-
def passed?
|
12
|
-
status == :passed
|
13
|
-
end
|
14
|
-
|
15
|
-
def failed?
|
16
|
-
status == :failed
|
17
|
-
end
|
18
|
-
|
19
|
-
def progress
|
20
|
-
Cucumber::ProgressString.format(status)
|
21
|
-
end
|
22
|
-
|
23
|
-
private
|
24
|
-
def serialized(e)
|
25
|
-
e.backtrace and e.backtrace.unshift e.backtrace.shift.sub(Dir.pwd, '.')
|
26
|
-
SerializedException.new(e.class, e.message, e.backtrace)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
data/lib/flatware/rspec.rb
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'rspec/core'
|
4
|
-
require 'rspec/expectations'
|
5
|
-
require 'flatware/rspec/cli'
|
6
|
-
|
7
|
-
module Flatware
|
8
|
-
module RSpec
|
9
|
-
require 'flatware/rspec/formatters/console'
|
10
|
-
require 'flatware/rspec/formatter'
|
11
|
-
require 'flatware/rspec/job_builder'
|
12
|
-
|
13
|
-
def self.extract_jobs_from_args(args, workers:)
|
14
|
-
JobBuilder.new(args, workers: workers).jobs
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.run(job, _options = {})
|
18
|
-
runner = ::RSpec::Core::Runner
|
19
|
-
def runner.trap_interrupt() end
|
20
|
-
|
21
|
-
args = %w[
|
22
|
-
--format Flatware::RSpec::Formatter
|
23
|
-
] + Array(job)
|
24
|
-
|
25
|
-
runner.run(args, $stderr, $stdout)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
require 'flatware/rspec/examples_notification'
|
2
|
-
|
3
|
-
module Flatware
|
4
|
-
module RSpec
|
5
|
-
class Checkpoint
|
6
|
-
attr_reader :summary, :failures_notification
|
7
|
-
|
8
|
-
def initialize(summary, failures_notification)
|
9
|
-
@summary, @failures_notification = summary, ExamplesNotification.new(failures_notification.failure_notifications)
|
10
|
-
end
|
11
|
-
|
12
|
-
def +(other)
|
13
|
-
self.class.new summary + other.summary, failures_notification + other.failures_notification
|
14
|
-
end
|
15
|
-
|
16
|
-
def failures?
|
17
|
-
summary.failure_count > 0 || summary.errors_outside_of_examples_count > 0
|
18
|
-
end
|
19
|
-
|
20
|
-
def failure_notifications
|
21
|
-
failures_notification.failure_notifications
|
22
|
-
end
|
23
|
-
|
24
|
-
def fully_formatted_failed_examples(*args)
|
25
|
-
failures_notification.fully_formatted_failed_examples(*args)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
data/lib/flatware/rspec/cli.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
require 'flatware/cli'
|
2
|
-
|
3
|
-
module Flatware
|
4
|
-
class CLI
|
5
|
-
worker_option
|
6
|
-
method_option 'dispatch-endpoint', type: :string, default: 'ipc://dispatch'
|
7
|
-
method_option 'sink-endpoint', type: :string, default: 'ipc://task'
|
8
|
-
desc "rspec [FLATWARE_OPTS]", "parallelizes rspec"
|
9
|
-
def rspec(*rspec_args)
|
10
|
-
jobs = RSpec.extract_jobs_from_args rspec_args, workers: workers
|
11
|
-
Flatware.verbose = options[:log]
|
12
|
-
Worker.spawn count: workers, runner: RSpec, dispatch: options['dispatch-endpoint'], sink: options['sink-endpoint']
|
13
|
-
start_sink jobs: jobs, workers: workers, formatter: Flatware::RSpec::Formatters::Console.new($stdout, $stderr)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
require 'rspec/core/formatters/console_codes'
|
2
|
-
|
3
|
-
module Flatware
|
4
|
-
module RSpec
|
5
|
-
class ExampleNotification
|
6
|
-
attr_reader :formatted
|
7
|
-
def initialize(notification)
|
8
|
-
@formatted = notification.fully_formatted '!', default_colorizer
|
9
|
-
end
|
10
|
-
|
11
|
-
def fully_formatted(i, _=nil)
|
12
|
-
formatted.sub '!', i.to_s
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
def default_colorizer
|
17
|
-
::RSpec::Core::Formatters::ConsoleCodes
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
require 'flatware/rspec/example_notification'
|
2
|
-
module Flatware
|
3
|
-
module RSpec
|
4
|
-
class ExamplesNotification
|
5
|
-
attr_reader :failure_notifications
|
6
|
-
|
7
|
-
def initialize(failure_notifications)
|
8
|
-
@failure_notifications = failure_notifications.map(&ExampleNotification.method(:new))
|
9
|
-
end
|
10
|
-
|
11
|
-
def +(other)
|
12
|
-
self.class.new failure_notifications + other.failure_notifications
|
13
|
-
end
|
14
|
-
|
15
|
-
def fully_formatted_failed_examples(*)
|
16
|
-
formatted = "\n\nFailures:\n"
|
17
|
-
failure_notifications.each_with_index do |failure, index|
|
18
|
-
formatted << failure.fully_formatted(index.next)
|
19
|
-
end
|
20
|
-
formatted
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
@@ -1,50 +0,0 @@
|
|
1
|
-
require 'flatware/rspec/checkpoint'
|
2
|
-
require 'flatware/rspec/summary'
|
3
|
-
require 'rspec/core/formatters/console_codes'
|
4
|
-
|
5
|
-
module Flatware
|
6
|
-
module RSpec
|
7
|
-
ProgressMessage = Struct.new(:progress)
|
8
|
-
|
9
|
-
class Formatter
|
10
|
-
attr_reader :summary, :output
|
11
|
-
|
12
|
-
def initialize(stdout)
|
13
|
-
@output = stdout
|
14
|
-
end
|
15
|
-
|
16
|
-
def example_passed(example)
|
17
|
-
send_progress :passed
|
18
|
-
end
|
19
|
-
|
20
|
-
def example_failed(example)
|
21
|
-
send_progress :failed
|
22
|
-
end
|
23
|
-
|
24
|
-
def example_pending(example)
|
25
|
-
send_progress :pending
|
26
|
-
end
|
27
|
-
|
28
|
-
def dump_summary(summary)
|
29
|
-
@summary = Summary.from_notification(summary)
|
30
|
-
end
|
31
|
-
|
32
|
-
def dump_failures(failure_notification)
|
33
|
-
@failure_notification = failure_notification
|
34
|
-
end
|
35
|
-
|
36
|
-
def close(*)
|
37
|
-
Sink::client.checkpoint Checkpoint.new(summary, @failure_notification)
|
38
|
-
@failure_notification = nil
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
def send_progress(status)
|
44
|
-
Sink::client.progress ProgressMessage.new status
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
::RSpec::Core::Formatters.register Formatter, :example_passed, :example_failed, :example_pending, :dump_summary, :dump_failures, :close
|
49
|
-
end
|
50
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
module Flatware::RSpec::Formatters
|
2
|
-
class Console
|
3
|
-
attr_reader :formatter
|
4
|
-
|
5
|
-
def initialize(out, err)
|
6
|
-
::RSpec::configuration.tty = true
|
7
|
-
::RSpec::configuration.color = true
|
8
|
-
@formatter = ::RSpec::Core::Formatters::ProgressFormatter.new(out)
|
9
|
-
end
|
10
|
-
|
11
|
-
def progress(result)
|
12
|
-
formatter.send(message_for(result),nil)
|
13
|
-
end
|
14
|
-
|
15
|
-
def summarize(checkpoints)
|
16
|
-
result = checkpoints.reduce :+
|
17
|
-
if result
|
18
|
-
formatter.dump_failures result
|
19
|
-
formatter.dump_summary result.summary
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
def message_for(result)
|
26
|
-
{
|
27
|
-
passed: :example_passed,
|
28
|
-
failed: :example_failed,
|
29
|
-
pending: :example_pending
|
30
|
-
}.fetch result.progress
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
@@ -1,110 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Flatware
|
4
|
-
module RSpec
|
5
|
-
# groups spec files into one job per worker.
|
6
|
-
# reads from persisted example statuses, if available,
|
7
|
-
# and attempts to ballence the jobs accordingly.
|
8
|
-
class JobBuilder
|
9
|
-
extend Forwardable
|
10
|
-
attr_reader :args, :workers, :configuration
|
11
|
-
def_delegators(
|
12
|
-
:configuration,
|
13
|
-
:files_to_run,
|
14
|
-
:example_status_persistence_file_path
|
15
|
-
)
|
16
|
-
|
17
|
-
def initialize(args, workers:)
|
18
|
-
@args = args
|
19
|
-
@workers = workers
|
20
|
-
|
21
|
-
@configuration = ::RSpec.configuration
|
22
|
-
configuration.define_singleton_method(:command) { 'rspec' }
|
23
|
-
|
24
|
-
::RSpec::Core::ConfigurationOptions.new(args).configure(@configuration)
|
25
|
-
end
|
26
|
-
|
27
|
-
def jobs
|
28
|
-
bucket_count = [files_to_run.size, workers].min
|
29
|
-
|
30
|
-
seconds_per_file = load_persisted_example_statuses
|
31
|
-
.select(&passing)
|
32
|
-
.map(&parse_example)
|
33
|
-
.reduce({}, &sum_by_example_file)
|
34
|
-
|
35
|
-
timed_files, untimed_files = files_to_run
|
36
|
-
.map(&method(:normalize_path))
|
37
|
-
.reduce(
|
38
|
-
[[], []]
|
39
|
-
) do |(timed, untimed), file|
|
40
|
-
if (time = seconds_per_file[file])
|
41
|
-
[timed.append([file, time]), untimed]
|
42
|
-
else
|
43
|
-
[timed, untimed.append(file)]
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
balance_by(bucket_count, timed_files, &:last)
|
48
|
-
.map { |bucket| bucket.map(&:first) }
|
49
|
-
.zip(
|
50
|
-
round_robin(bucket_count, untimed_files)
|
51
|
-
).map(&:flatten)
|
52
|
-
.map do |files|
|
53
|
-
Job.new(files, args)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
private
|
58
|
-
|
59
|
-
def normalize_path(path)
|
60
|
-
::RSpec::Core::Metadata.relative_path(File.expand_path(path))
|
61
|
-
end
|
62
|
-
|
63
|
-
def load_persisted_example_statuses
|
64
|
-
::RSpec::Core::ExampleStatusPersister.load_from(
|
65
|
-
example_status_persistence_file_path || ''
|
66
|
-
)
|
67
|
-
end
|
68
|
-
|
69
|
-
def sum_by_example_file
|
70
|
-
lambda do |times, file_name:, seconds:|
|
71
|
-
times.merge(file_name => seconds) { |_, old = 0, new| old + new }
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def passing
|
76
|
-
->(status:, **) { status =~ /pass/i }
|
77
|
-
end
|
78
|
-
|
79
|
-
def parse_example
|
80
|
-
lambda do |example_id:, run_time:, **|
|
81
|
-
seconds = run_time.match(/\d+(\.\d+)?/).to_s.to_f
|
82
|
-
file_name = ::RSpec::Core::Example.parse_id(example_id).first
|
83
|
-
{ seconds: seconds, file_name: file_name }
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
def round_robin(count, items)
|
88
|
-
Array.new(count) { [] }.tap do |groups|
|
89
|
-
items.each_with_index do |entry, i|
|
90
|
-
groups[i % count] << entry
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def balance_by(count, items, &block)
|
96
|
-
# find the group with the smallest sum and add it there
|
97
|
-
Array.new(count) { [] }.tap do |groups|
|
98
|
-
items
|
99
|
-
.sort_by(&block)
|
100
|
-
.reverse
|
101
|
-
.each do |entry|
|
102
|
-
groups.min_by do |group|
|
103
|
-
group.map(&block).reduce(:+) || 0
|
104
|
-
end.push(entry)
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
require 'rspec/core/notifications'
|
2
|
-
module Flatware
|
3
|
-
module RSpec
|
4
|
-
class Example
|
5
|
-
attr_reader :location_rerun_argument, :full_description
|
6
|
-
def initialize(rspec_example)
|
7
|
-
@full_description = rspec_example.full_description
|
8
|
-
@location_rerun_argument = rspec_example.location_rerun_argument
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
class Summary < ::RSpec::Core::Notifications::SummaryNotification
|
13
|
-
def +(other)
|
14
|
-
self.class.new(*zip(other).map {|a,b| a + b})
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.from_notification(summary)
|
18
|
-
serialized_examples = [summary.examples, summary.failed_examples, summary.pending_examples].map do |examples|
|
19
|
-
examples.map(&Example.method(:new))
|
20
|
-
end
|
21
|
-
|
22
|
-
new summary.duration, *serialized_examples, *summary.to_a[4..-1]
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|