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 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