flatware 1.1.0 → 1.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 81d5bd7d6b9f6e71cf86030bdb1e10e9f699b8e63053b7795573438df8aa1ba9
4
- data.tar.gz: 141903472d1d3d2998f1a19fec837fc79dfca9a966996c91ac5b16986e1c4906
3
+ metadata.gz: 9da02a0ce1009b7c0fc4c03cd33af10e5e2984b706ef46aca34dd8f33c593267
4
+ data.tar.gz: 94563ca34708d9d7563f36291eb781ab6f48654a09a0fe7d7912d04aae008a5b
5
5
  SHA512:
6
- metadata.gz: 11985434629de54efbb5ad7d1b14ecb27bdd9047a72893a2367d0747e60bc67042e876d3374ab39c85b2c5617312243e92458c62f03ccb372b371e76cacda5ec
7
- data.tar.gz: e763a95035658051651a8c44b1a40ea014a9e9f41b8a08faa13c10fd1cfe7985a27771bf9ebc3ba8178b163ddab2847644b5f522a4a889c15baed371897a0c97
6
+ metadata.gz: 9ac813bab61e13d630fde64a875e57eb973fd74c6a59d48772ab1ec2d5a688a99899b70e4874c9fcdd71348f88a4b668a97955fdea156c667968fe2ca63523fe
7
+ data.tar.gz: d7af5aa56a6ffd45043834440c144f8baebc7c7b59a6fc1d80e93dddc959d20f3db3b6cb66cd183e3f055898abbcd09ec9864266ed91f8df361efae15f70671e
@@ -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: "-w", type: :numeric, default: processors, desc: "Number of concurent processes to run"
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: "-l", type: :boolean, desc: "Print debug messages to $stderr"
15
+ class_option :log, aliases: '-l', type: :boolean, desc: 'Print debug messages to $stderr'
15
16
 
16
17
  worker_option
17
- desc "fan [COMMAND]", "executes the given job on all of the workers"
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({"TEST_ENV_NUMBER" => i.to_s}, command)
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 "clear", "kills all flatware processes"
33
+ desc 'clear', 'kills all flatware processes'
33
34
  def clear
34
- (Flatware.pids - [$$]).each do |pid|
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
- $0 = 'flatware sink'
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 = Gem.loaded_specs.keys.grep(/^flatware-/)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Flatware
4
- VERSION = '1.1.0'
4
+ VERSION = '1.2.0'
5
5
  end
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.1.0
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-06-01 00:00:00.000000000 Z
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 cucumber and rspec runner
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
- rubyforge_project:
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 cucumber and rspec runner
116
+ summary: A distributed rspec and cucumber runner
138
117
  test_files: []
@@ -1 +0,0 @@
1
- require 'flatware/cucumber'
@@ -1 +0,0 @@
1
- require 'flatware/rspec'
@@ -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
@@ -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
@@ -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