sleeping_king_studios-tasks 0.1.0.rc.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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +26 -0
  3. data/DEVELOPMENT.md +50 -0
  4. data/LICENSE +22 -0
  5. data/README.md +405 -0
  6. data/lib/sleeping_king_studios/tasks.rb +27 -0
  7. data/lib/sleeping_king_studios/tasks/apps.rb +49 -0
  8. data/lib/sleeping_king_studios/tasks/apps/app_configuration.rb +68 -0
  9. data/lib/sleeping_king_studios/tasks/apps/applications_task.rb +28 -0
  10. data/lib/sleeping_king_studios/tasks/apps/bundle.rb +8 -0
  11. data/lib/sleeping_king_studios/tasks/apps/bundle/install_runner.rb +21 -0
  12. data/lib/sleeping_king_studios/tasks/apps/bundle/install_task.rb +39 -0
  13. data/lib/sleeping_king_studios/tasks/apps/bundle/update_runner.rb +21 -0
  14. data/lib/sleeping_king_studios/tasks/apps/bundle/update_task.rb +39 -0
  15. data/lib/sleeping_king_studios/tasks/apps/ci.rb +8 -0
  16. data/lib/sleeping_king_studios/tasks/apps/ci/results_reporter.rb +69 -0
  17. data/lib/sleeping_king_studios/tasks/apps/ci/rspec_task.rb +29 -0
  18. data/lib/sleeping_king_studios/tasks/apps/ci/rspec_wrapper.rb +42 -0
  19. data/lib/sleeping_king_studios/tasks/apps/ci/rubocop_task.rb +29 -0
  20. data/lib/sleeping_king_studios/tasks/apps/ci/rubocop_wrapper.rb +29 -0
  21. data/lib/sleeping_king_studios/tasks/apps/ci/simplecov_task.rb +68 -0
  22. data/lib/sleeping_king_studios/tasks/apps/ci/step_wrapper.rb +49 -0
  23. data/lib/sleeping_king_studios/tasks/apps/ci/steps_runner.rb +37 -0
  24. data/lib/sleeping_king_studios/tasks/apps/ci/steps_task.rb +92 -0
  25. data/lib/sleeping_king_studios/tasks/ci.rb +8 -0
  26. data/lib/sleeping_king_studios/tasks/ci/cucumber_parser.rb +118 -0
  27. data/lib/sleeping_king_studios/tasks/ci/cucumber_results.rb +191 -0
  28. data/lib/sleeping_king_studios/tasks/ci/cucumber_runner.rb +53 -0
  29. data/lib/sleeping_king_studios/tasks/ci/cucumber_task.rb +47 -0
  30. data/lib/sleeping_king_studios/tasks/ci/results_helpers.rb +44 -0
  31. data/lib/sleeping_king_studios/tasks/ci/rspec_each_results.rb +118 -0
  32. data/lib/sleeping_king_studios/tasks/ci/rspec_each_task.rb +156 -0
  33. data/lib/sleeping_king_studios/tasks/ci/rspec_results.rb +126 -0
  34. data/lib/sleeping_king_studios/tasks/ci/rspec_runner.rb +62 -0
  35. data/lib/sleeping_king_studios/tasks/ci/rspec_task.rb +71 -0
  36. data/lib/sleeping_king_studios/tasks/ci/rubocop_results.rb +80 -0
  37. data/lib/sleeping_king_studios/tasks/ci/rubocop_runner.rb +46 -0
  38. data/lib/sleeping_king_studios/tasks/ci/rubocop_task.rb +44 -0
  39. data/lib/sleeping_king_studios/tasks/ci/simplecov_results.rb +62 -0
  40. data/lib/sleeping_king_studios/tasks/ci/simplecov_task.rb +25 -0
  41. data/lib/sleeping_king_studios/tasks/ci/steps_runner.rb +69 -0
  42. data/lib/sleeping_king_studios/tasks/ci/steps_task.rb +93 -0
  43. data/lib/sleeping_king_studios/tasks/configuration.rb +114 -0
  44. data/lib/sleeping_king_studios/tasks/file.rb +8 -0
  45. data/lib/sleeping_king_studios/tasks/file/new_task.rb +238 -0
  46. data/lib/sleeping_king_studios/tasks/process_runner.rb +70 -0
  47. data/lib/sleeping_king_studios/tasks/task.rb +95 -0
  48. data/lib/sleeping_king_studios/tasks/task_group.rb +37 -0
  49. data/lib/sleeping_king_studios/tasks/version.rb +58 -0
  50. metadata +271 -0
@@ -0,0 +1,29 @@
1
+ # lib/sleeping_king_studios/tasks/apps/ci/rubocop_task.rb
2
+
3
+ require 'sleeping_king_studios/tasks/apps/ci'
4
+ require 'sleeping_king_studios/tasks/apps/ci/steps_task'
5
+
6
+ module SleepingKingStudios::Tasks::Apps::Ci
7
+ # Defines a Thor task for running the RuboCop linter for each application.
8
+ class RuboCopTask < SleepingKingStudios::Tasks::Task
9
+ def self.description
10
+ 'Runs the RuboCop linter for each application.'
11
+ end # class method description
12
+
13
+ def self.task_name
14
+ 'rubocop'
15
+ end # class method task_name
16
+
17
+ option :quiet,
18
+ :aliases => '-q',
19
+ :type => :boolean,
20
+ :default => false,
21
+ :desc => 'Do not write lint results to STDOUT.'
22
+
23
+ def call *applications
24
+ SleepingKingStudios::Tasks::Apps::Ci::StepsTask.
25
+ new(options.merge('only' => %w(rubocop))).
26
+ call(*applications)
27
+ end # method call
28
+ end # class
29
+ end # module
@@ -0,0 +1,29 @@
1
+ # lib/sleeping_king_studios/tasks/apps/ci/rubocop_wrapper.rb
2
+
3
+ require 'sleeping_king_studios/tasks/apps/ci'
4
+ require 'sleeping_king_studios/tasks/apps/ci/step_wrapper'
5
+ require 'sleeping_king_studios/tasks/ci/rubocop_results'
6
+
7
+ module SleepingKingStudios::Tasks::Apps::Ci
8
+ # Wrapper class for calling a RuboCop Ci task for a specific application.
9
+ class RuboCopWrapper < SleepingKingStudios::Tasks::Apps::Ci::StepWrapper
10
+ def call application
11
+ super
12
+
13
+ run_step(*source_files)
14
+ end # method call
15
+
16
+ private
17
+
18
+ def source_files
19
+ config =
20
+ SleepingKingStudios::Tasks::Apps.configuration[current_application]
21
+
22
+ config.source_files + config.spec_files
23
+ end # method source_files
24
+
25
+ def step_key
26
+ :rubocop
27
+ end # method step_key
28
+ end # module
29
+ end # module
@@ -0,0 +1,68 @@
1
+ # lib/sleeping_king_studios/tasks/apps/ci/simplecov_task.rb
2
+
3
+ require 'sleeping_king_studios/tasks/apps/ci'
4
+ require 'sleeping_king_studios/tasks/ci/simplecov_results'
5
+ require 'sleeping_king_studios/tasks/task'
6
+
7
+ module SleepingKingStudios::Tasks::Apps::Ci
8
+ # Defines a Thor task for aggregating SimpleCov coverage results across
9
+ # applications.
10
+ class SimpleCovTask < SleepingKingStudios::Tasks::Task
11
+ RESULTS_STRUCT =
12
+ Struct.new(
13
+ :covered_lines,
14
+ :covered_percent,
15
+ :missed_lines,
16
+ :total_lines
17
+ ) # end struct
18
+
19
+ def self.configure_simplecov!
20
+ require 'simplecov'
21
+ require 'simplecov-json'
22
+
23
+ ::SimpleCov.configure do
24
+ command_name "#{command_name}:#{ENV['APP_NAME']}" if ENV['APP_NAME']
25
+
26
+ self.formatter =
27
+ ::SimpleCov::Formatter::MultiFormatter.new(
28
+ [formatter, ::SimpleCov::Formatter::JSONFormatter]
29
+ ) # end formatter
30
+ end # configure
31
+ end # class method configure_simplecov!
32
+
33
+ def self.description
34
+ 'Aggregates the SimpleCov results for all applications.'
35
+ end # class method description
36
+
37
+ def self.task_name
38
+ 'simplecov'
39
+ end # class method task_name
40
+
41
+ def call _application = nil
42
+ results = load_report :report => File.join('coverage', 'coverage.json')
43
+ results = convert_results_to_object(results)
44
+
45
+ SleepingKingStudios::Tasks::Ci::SimpleCovResults.new(results)
46
+ end # method call
47
+
48
+ private
49
+
50
+ def load_report report:
51
+ raw = File.read report
52
+ json = JSON.parse raw
53
+
54
+ json['metrics']
55
+ rescue
56
+ {}
57
+ end # method load_report
58
+
59
+ def convert_results_to_object hsh
60
+ RESULTS_STRUCT.new(
61
+ hsh.fetch('covered_lines', 0),
62
+ hsh.fetch('covered_percent', 0.0),
63
+ hsh.fetch('total_lines', 0) - hsh.fetch('covered_lines', 0),
64
+ hsh.fetch('total_lines', 0)
65
+ ) # end object
66
+ end # method convert_results_to_object
67
+ end # module
68
+ end # module
@@ -0,0 +1,49 @@
1
+ # lib/sleeping_king_studios/tasks/apps/ci/step_wrapper.rb
2
+
3
+ require 'sleeping_king_studios/tasks/apps/applications_task'
4
+ require 'sleeping_king_studios/tasks/apps/ci'
5
+
6
+ module SleepingKingStudios::Tasks::Apps::Ci
7
+ # Wrapper class for calling a configured task for a specific application.
8
+ class StepWrapper < SleepingKingStudios::Tasks::Task
9
+ include SleepingKingStudios::Tasks::Apps::ApplicationsTask
10
+
11
+ def call application, *_rest
12
+ @current_application = application
13
+ @step_config = nil
14
+ end # method call
15
+
16
+ private
17
+
18
+ attr_reader :current_application
19
+
20
+ def build_step
21
+ require step_config.fetch(:require) if step_config.key?(:require)
22
+
23
+ step_class = Object.const_get(step_config.fetch :class)
24
+
25
+ step_class.new(step_options)
26
+ end # method
27
+
28
+ def run_step *args
29
+ return if skip_step?
30
+
31
+ build_step.call(*args)
32
+ end # method run_step
33
+
34
+ def skip_step?
35
+ step_config == false
36
+ end # method skip_step?
37
+
38
+ def step_config
39
+ config = SleepingKingStudios::Tasks.configuration
40
+ steps = config.ci.steps_with_options
41
+
42
+ steps.fetch(step_key, false)
43
+ end # method step_config
44
+
45
+ def step_options
46
+ options
47
+ end # method step_options
48
+ end # class
49
+ end # module
@@ -0,0 +1,37 @@
1
+ # lib/sleeping_king_studios/tasks/apps/ci/steps_runner.rb
2
+
3
+ require 'sleeping_king_studios/tasks/apps/applications_task'
4
+ require 'sleeping_king_studios/tasks/ci/steps_runner'
5
+
6
+ module SleepingKingStudios::Tasks::Apps::Ci
7
+ # Abstract base class for running a sequence of tasks for a specific
8
+ # application from a configured list.
9
+ class StepsRunner < SleepingKingStudios::Tasks::Ci::StepsRunner
10
+ include SleepingKingStudios::Tasks::Apps::ApplicationsTask
11
+
12
+ def call application
13
+ @current_application = application
14
+
15
+ super application
16
+ end # method call
17
+
18
+ private
19
+
20
+ attr_reader :current_application
21
+
22
+ def ci_steps
23
+ SleepingKingStudios::Tasks::Apps.configuration[current_application].
24
+ ci.steps_with_options
25
+ end # method ci_steps
26
+
27
+ def skip_step? _name, config
28
+ return true if config == false
29
+
30
+ if options.fetch('global', false)
31
+ !config.fetch(:global, false)
32
+ else
33
+ config.fetch(:global, false)
34
+ end # if-else
35
+ end # method skip_step?
36
+ end # class
37
+ end # module
@@ -0,0 +1,92 @@
1
+ # lib/sleeping_king_studios/tasks/apps/ci/steps_task.rb
2
+
3
+ require 'sleeping_king_studios/tasks/apps/applications_task'
4
+ require 'sleeping_king_studios/tasks/apps/ci'
5
+ require 'sleeping_king_studios/tasks/apps/ci/results_reporter'
6
+ require 'sleeping_king_studios/tasks/apps/ci/steps_runner'
7
+
8
+ module SleepingKingStudios::Tasks::Apps::Ci
9
+ # Thor task for running each step in the CI suite for each application and
10
+ # generating a report.
11
+ class StepsTask < SleepingKingStudios::Tasks::Task
12
+ include SleepingKingStudios::Tasks::Apps::ApplicationsTask
13
+
14
+ def self.description
15
+ 'Runs the configured steps for each application.'
16
+ end # class method description
17
+
18
+ option :except,
19
+ :type => :array,
20
+ :default => [],
21
+ :desc => 'Exclude steps from the CI process.'
22
+ option :only,
23
+ :type => :array,
24
+ :default => [],
25
+ :desc => 'Run only the specified steps from the CI process.'
26
+ option :quiet,
27
+ :aliases => '-q',
28
+ :type => :boolean,
29
+ :default => false,
30
+ :desc => 'Do not write intermediate results to STDOUT.'
31
+
32
+ def call *applications
33
+ filtered = filter_applications :only => applications
34
+ results = run_steps(filtered)
35
+ globals = run_global_steps
36
+
37
+ aggregate_results(results) if filtered.count > 1
38
+
39
+ (results['Totals'] ||= {}).update(globals)
40
+
41
+ say "\n" unless quiet?
42
+
43
+ reporter = ResultsReporter.new(self)
44
+ reporter.call(results)
45
+
46
+ results
47
+ end # method call
48
+
49
+ private
50
+
51
+ def aggregate_results results
52
+ totals = Hash.new { |hsh, key| hsh[key] = 0 }
53
+
54
+ results.each do |_, app_results|
55
+ app_results.each do |step, step_results|
56
+ next if step_results.nil?
57
+
58
+ next totals[step] = step_results unless totals.key?(step)
59
+
60
+ totals[step] = totals[step].merge(step_results)
61
+ end # each
62
+ end # each
63
+
64
+ results['Totals'] = totals
65
+ end # method aggregate_results
66
+
67
+ def run_global_steps
68
+ opts = options.merge 'global' => true
69
+ runner = SleepingKingStudios::Tasks::Apps::Ci::StepsRunner.new(opts)
70
+
71
+ runner.call(nil)
72
+ end # method run_global_steps
73
+
74
+ def run_steps applications
75
+ results = Hash.new { |hsh, key| hsh[key] = {} }
76
+
77
+ applications.each do |name, _|
78
+ results[name] = run_steps_for_application(name)
79
+ end # each
80
+
81
+ results
82
+ end # method run_steps
83
+
84
+ def run_steps_for_application name
85
+ steps_runner.call(name)
86
+ end # method run_steps_for_application
87
+
88
+ def steps_runner
89
+ SleepingKingStudios::Tasks::Apps::Ci::StepsRunner.new(options)
90
+ end # method steps_runner
91
+ end # class
92
+ end # module
@@ -0,0 +1,8 @@
1
+ # lib/sleeping_king_studios/tasks/ci.rb
2
+
3
+ require 'sleeping_king_studios/tasks'
4
+
5
+ module SleepingKingStudios::Tasks
6
+ # Thor tasks for continuous integration.
7
+ module Ci; end
8
+ end # module
@@ -0,0 +1,118 @@
1
+ # lib/sleeping_king_studios/tasks/ci/cucumber_parser.rb
2
+
3
+ require 'sleeping_king_studios/tasks/ci'
4
+
5
+ module SleepingKingStudios::Tasks::Ci
6
+ # Parses the output from cucumber --format=json into a summary report.
7
+ module CucumberParser
8
+ class << self
9
+ def parse results
10
+ @report = build_report
11
+
12
+ results.each { |feature| parse_feature(feature) }
13
+
14
+ report['duration'] = report['duration'] / (1.0 * 10**9)
15
+
16
+ report
17
+ end # class method parse
18
+
19
+ private
20
+
21
+ attr_reader :report
22
+
23
+ def build_report
24
+ {
25
+ 'scenario_count' => 0,
26
+ 'failing_scenarios' => [],
27
+ 'pending_scenarios' => [],
28
+ 'step_count' => 0,
29
+ 'failing_step_count' => 0,
30
+ 'pending_step_count' => 0,
31
+ 'duration' => 0
32
+ } # end report
33
+ end # method build report
34
+
35
+ def parse_feature feature
36
+ feature['elements'].each do |scenario|
37
+ parse_scenario(scenario, feature)
38
+ end # each
39
+ end # method parse_feature
40
+
41
+ # rubocop:disable Metrics/MethodLength
42
+ def parse_scenario scenario, feature
43
+ failing = false
44
+ pending = false
45
+
46
+ scenario['steps'].each do |step|
47
+ status = parse_step(step)
48
+
49
+ failing ||= status == 'failed'
50
+ pending ||= status == 'pending' || status == 'skipped'
51
+ end # each
52
+
53
+ report_scenario(
54
+ scenario,
55
+ feature,
56
+ :failing => failing,
57
+ :pending => pending
58
+ ) # end report scenario
59
+ end # method parse_scenario
60
+ # rubocop:enable Metrics/MethodLength
61
+
62
+ def parse_step step
63
+ report['step_count'] += 1
64
+ report['duration'] += step_duration(step)
65
+
66
+ status = step_status(step) || 'failed'
67
+
68
+ if status == 'failed'
69
+ report['failing_step_count'] += 1
70
+ elsif status == 'pending' || status == 'skipped'
71
+ report['pending_step_count'] += 1
72
+ end # if
73
+
74
+ status
75
+ end # method parse_step
76
+
77
+ # rubocop:disable Metrics/MethodLength
78
+ def report_scenario scenario, feature, failing:, pending:
79
+ report['scenario_count'] += 1
80
+
81
+ return unless failing || pending
82
+
83
+ hsh =
84
+ {
85
+ 'location' => "#{feature['uri']}:#{scenario['line']}",
86
+ 'description' => "#{scenario['keyword']}: #{scenario['name']}"
87
+ }
88
+
89
+ if failing
90
+ report['failing_scenarios'] << hsh
91
+ else
92
+ report['pending_scenarios'] << hsh
93
+ end # if-else
94
+ end # method report_scenario
95
+ # rubocop:enable Metrics/MethodLength
96
+
97
+ def step_duration hsh
98
+ return 0 if hsh.nil? || hsh.empty?
99
+
100
+ result = hsh['result']
101
+
102
+ return 0 if result.nil? || result.empty?
103
+
104
+ result['duration'] || 0
105
+ end # method step_duration
106
+
107
+ def step_status hsh
108
+ return nil if hsh.nil? || hsh.empty?
109
+
110
+ result = hsh['result']
111
+
112
+ return nil if result.nil? || result.empty?
113
+
114
+ result['status']
115
+ end # method step_status
116
+ end # class
117
+ end # module
118
+ end # module
@@ -0,0 +1,191 @@
1
+ # lib/sleeping_king_studios/tasks/ci/cucumber_results.rb
2
+
3
+ require 'sleeping_king_studios/tasks/ci'
4
+
5
+ module SleepingKingStudios::Tasks::Ci
6
+ # rubocop:disable Metrics/ClassLength
7
+
8
+ # Encapsulates the results of a Cucumber call.
9
+ class CucumberResults
10
+ # @param results [Hash] The raw results of the RSpec call.
11
+ def initialize results
12
+ @results = results
13
+ end # constructor
14
+
15
+ # @param other [RSpecResults] The other results object to compare.
16
+ #
17
+ # @return [Boolean] True if the results are equal, otherwise false.
18
+ def == other
19
+ if other.is_a?(Hash)
20
+ empty? ? other.empty? : to_h == other
21
+ elsif other.is_a?(CucumberResults)
22
+ to_h == other.to_h
23
+ else
24
+ false
25
+ end # if-elsif-else
26
+ end # method ==
27
+
28
+ # @return [Float] The duration value.
29
+ def duration
30
+ @results.fetch('duration', 0.0)
31
+ end # method duration
32
+
33
+ # @return [Boolean] True if there are no scenarios, otherwise false.
34
+ def empty?
35
+ scenario_count.zero?
36
+ end # method empty?
37
+
38
+ # @return [Boolean] True if there are any failing scenarios, otherwise
39
+ # false.
40
+ def failing?
41
+ !failing_scenario_count.zero?
42
+ end # method failing?
43
+
44
+ # @return [Integer] The list of failing scenarios.
45
+ def failing_scenarios
46
+ @results.fetch('failing_scenarios', [])
47
+ end # method failing_scenarios
48
+
49
+ # @return [Integer] The number of failing scenarios.
50
+ def failing_scenario_count
51
+ failing_scenarios.count
52
+ end # method failing_scenario_count
53
+
54
+ # @return [Integer] The number of failing steps.
55
+ def failing_step_count
56
+ @results.fetch('failing_step_count', 0)
57
+ end # method failing_step_count
58
+
59
+ # Adds the given result values and returns a new results object with the
60
+ # sums.
61
+ #
62
+ # @param other [RSpecResults] The results to add.
63
+ #
64
+ # @return [RSpecResults] The total results.
65
+ def merge other
66
+ merged = {}
67
+
68
+ keys.each do |key|
69
+ merged[key] = public_send(key) + other.public_send(key)
70
+ end # each
71
+
72
+ self.class.new(merged)
73
+ end # method merge
74
+
75
+ # @return [Boolean] True if there are any pending scenarios, otherwise
76
+ # false.
77
+ def pending?
78
+ !pending_scenario_count.zero?
79
+ end # method pending?
80
+
81
+ # @return [Integer] The number of pending scenarios.
82
+ def pending_scenario_count
83
+ pending_scenarios.count
84
+ end # method pending_scenario_count
85
+
86
+ # @return [Integer] The list of pending scenarios.
87
+ def pending_scenarios
88
+ @results.fetch('pending_scenarios', [])
89
+ end # method pending_scenarios
90
+
91
+ # @return [Integer] The number of pending steps.
92
+ def pending_step_count
93
+ @results.fetch('pending_step_count', 0)
94
+ end # method pending_step_count
95
+
96
+ # @return [Integer] The total number of scenarios.
97
+ def scenario_count
98
+ @results.fetch('scenario_count', 0)
99
+ end # method scenario_count
100
+
101
+ # @return [String] A brief summary of the scenario results.
102
+ def scenarios_summary
103
+ build_summary(
104
+ 'scenario',
105
+ scenario_count,
106
+ failing_scenario_count,
107
+ pending_scenario_count
108
+ ) # end build_summary
109
+ end # method scenarios_summary
110
+
111
+ # @return [Integer] The total number of steps.
112
+ def step_count
113
+ @results.fetch('step_count', 0)
114
+ end # method step_count
115
+
116
+ # @return [String] A brief summary of the scenario results.
117
+ def steps_summary
118
+ build_summary(
119
+ 'step',
120
+ step_count,
121
+ failing_step_count,
122
+ pending_step_count
123
+ ) # end build_summary
124
+ end # method steps_summary
125
+
126
+ # @return [String] A brief summary of the results.
127
+ def summary
128
+ str = scenarios_summary
129
+
130
+ str << ', '
131
+
132
+ str << steps_summary
133
+
134
+ str << " in #{duration.round(2)} seconds"
135
+ end # method summary
136
+ alias_method :to_s, :summary
137
+
138
+ # @return [Hash] The hash representation of the results.
139
+ def to_h
140
+ hsh = {}
141
+
142
+ keys.each { |key| hsh[key] = public_send(key) }
143
+
144
+ hsh
145
+ end # method to_h
146
+
147
+ private
148
+
149
+ def build_summary name, count, failing_count, pending_count
150
+ str = pluralize(count, name)
151
+
152
+ return str if failing_count.zero? && pending_count.zero?
153
+
154
+ str << build_summary_details(failing_count, pending_count)
155
+ end # method build_summary
156
+
157
+ def build_summary_details failing_count, pending_count
158
+ str = ' ('
159
+
160
+ str << pluralize(failing_count, 'failure') unless failing_count.zero?
161
+
162
+ str << ', ' if !failing_count.zero? && !pending_count.zero?
163
+
164
+ str << "#{pending_count} pending" unless pending_count.zero?
165
+
166
+ str << ')'
167
+ end # method build_summary_details
168
+
169
+ def keys
170
+ %w(
171
+ duration
172
+ step_count
173
+ pending_step_count
174
+ failing_step_count
175
+ scenario_count
176
+ pending_scenarios
177
+ failing_scenarios
178
+ ) # end keys
179
+ end # method keys
180
+
181
+ def pluralize count, singular, plural = nil
182
+ "#{count} #{tools.integer.pluralize count, singular, plural}"
183
+ end # method pluralize
184
+
185
+ def tools
186
+ SleepingKingStudios::Tools::Toolbelt.instance
187
+ end # method tools
188
+ end # class
189
+
190
+ # rubocop:enable Metrics/ClassLength
191
+ end # module