peak_flow_utils 0.1.14 → 0.1.17

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: 7e3d80778a6e2f090ed1247e8afd8b0cf95c1c57d96a01315534f32aa0a56d00
4
- data.tar.gz: b4ab6fd0ba591e3f750dca4d1fea08de84d2d04a150b0bf6a8a90c15928b4157
3
+ metadata.gz: 3917057ab2d29651eec619ccb60c5a6c671ce092ada22ff80269ce399a8a7bd6
4
+ data.tar.gz: 25738f4241971584cfbbc9ea80cbf07f426c5dac8b6394398eb92212c8818be1
5
5
  SHA512:
6
- metadata.gz: 9dc474ec28b8221fb5730bad562121ef0925e6bb2cd0f554e759d51a5daba2169bb0290253b10dfda1a140a616c21751f228c2994bcddf468c6a5bae3b7d741a
7
- data.tar.gz: 57b9245ce4892aa877dd01b7947f9db2a19a80cb1355a2a6ab57350779cd3c9090567417bd369399d8a4daba65c00d867c76fdb506b85e5342a330c3e79414bd
6
+ metadata.gz: 144f08cb6bb1af54806406e1a9ce16ade04bea01e07cf8c16537764236f9e58d672956a9346ddc6a5088f1ed8e8a3c87ff8d02ff797f230b74e47fa685590005
7
+ data.tar.gz: 324f5e6834f219cb4a51dd2c3a1df5321f4912e6586ea9a67c90d05554aeae231466b7da5dd23148cef8426aae93dc02862c91faf56c58a7fa1a1125c01b9e61
data/README.md CHANGED
@@ -39,6 +39,13 @@ Add this to `config/peakflow.rb`:
39
39
  PeakFlowUtils::NotifierRails.configure
40
40
  ```
41
41
 
42
+ ### Reporting ActiveJob errors in Rails:
43
+
44
+ If you want the job name and its arguments logged in parameters you can execute this service:
45
+ ```ruby
46
+ PeakFlowUtils::ActiveJobParametersLogging.execute!
47
+ ```
48
+
42
49
  ### Reporting Sidekiq errors in Rails:
43
50
 
44
51
  Add this to `config/peakflow.rb`:
@@ -46,6 +53,11 @@ Add this to `config/peakflow.rb`:
46
53
  PeakFlowUtils::NotifierSidekiq.configure
47
54
  ```
48
55
 
56
+ If you want the job name and its arguments logged in parameters you can execute this service:
57
+ ```ruby
58
+ PeakFlowUtils::SidekiqParametersLogging.execute!
59
+ ```
60
+
49
61
  ## Contributing
50
62
  Contribution directions go here.
51
63
 
@@ -0,0 +1,13 @@
1
+ class PeakFlowUtils::ActiveJobParametersLogging < PeakFlowUtils::ApplicationService
2
+ def perform
3
+ ActiveJob::Base.class_eval do
4
+ around_perform do |job, block|
5
+ PeakFlowUtils::Notifier.with_parameters(active_job: {job_name: job.class.name, job_arguments: job.arguments}) do
6
+ block.call
7
+ end
8
+ end
9
+ end
10
+
11
+ succeed!
12
+ end
13
+ end
@@ -0,0 +1,62 @@
1
+ class PeakFlowUtils::DeepMerger < PeakFlowUtils::ApplicationService
2
+ attr_reader :hashes, :object_mappings
3
+
4
+ def initialize(hashes:, object_mappings: {})
5
+ @hashes = hashes
6
+ @object_mappings = object_mappings
7
+ end
8
+
9
+ def perform
10
+ merged = {}
11
+
12
+ hashes.each do |hash|
13
+ merge_hash(hash, merged)
14
+ end
15
+
16
+ succeed! merged
17
+ end
18
+
19
+ def clone_something(object)
20
+ if object.is_a?(Hash)
21
+ new_hash = {}
22
+ merge_hash(object, new_hash)
23
+ new_hash
24
+ elsif object.is_a?(Array)
25
+ new_array = []
26
+ merge_array(object, new_array)
27
+ new_array
28
+ else
29
+ object
30
+ end
31
+ end
32
+
33
+ def merge_something(object, merged)
34
+ if object.is_a?(Array)
35
+ merge_array(object, merged)
36
+ elsif object.is_a?(Hash)
37
+ merge_hash(object, merged)
38
+ else
39
+ raise "Unknown object: #{object.class.name}"
40
+ end
41
+ end
42
+
43
+ def merge_array(array, merged)
44
+ array.each do |value|
45
+ merged << clone_something(value)
46
+ end
47
+ end
48
+
49
+ def merge_hash(hash, merged)
50
+ hash.each do |key, value|
51
+ if value.is_a?(Array)
52
+ merged[key] = []
53
+ merge_array(value, merged[key])
54
+ elsif value.is_a?(Hash)
55
+ merged[key] ||= {}
56
+ merge_hash(value, merged[key])
57
+ else
58
+ merged[key] = clone_something(value)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,16 @@
1
+ class PeakFlowUtils::SidekiqParametersLogging < PeakFlowUtils::ApplicationService
2
+ def perform
3
+ require "sidekiq"
4
+ require "sidekiq/processor"
5
+
6
+ Sidekiq::Processor.class_eval do
7
+ def execute_job(worker, cloned_args)
8
+ PeakFlowUtils::Notifier.with_parameters(sidekiq: {worker_class_name: worker.class.name, cloned_args: cloned_args}) do
9
+ worker.perform(*cloned_args)
10
+ end
11
+ end
12
+ end
13
+
14
+ succeed!
15
+ end
16
+ end
@@ -0,0 +1,84 @@
1
+ require "monitor"
2
+ require_relative "thread_callbacks_patch"
3
+
4
+ Thread.on_initialize do |parent:, thread:|
5
+ thread.instance_variable_set(:@_inherited_local_vars, parent.instance_variable_get(:@_inherited_local_vars))
6
+ end
7
+
8
+ Thread.class_eval do
9
+ def self.inherited_local_vars_mutex
10
+ @inherited_local_vars_mutex ||= Mutex.new
11
+ end
12
+
13
+ def self._inherited_local_vars
14
+ Thread.current.instance_variable_set(:@_inherited_local_vars, {}) unless Thread.current.instance_variable_get(:@_inherited_local_vars)
15
+ Thread.current.instance_variable_get(:@_inherited_local_vars)
16
+ end
17
+
18
+ def self.inherited_local_vars_reset
19
+ ObjectSpace.each_object(Thread) do |thread|
20
+ inherited_local_vars_mutex.synchronize do
21
+ thread.instance_variable_set(:@_inherited_local_vars, nil)
22
+ end
23
+ end
24
+ end
25
+
26
+ def self.inherited_local_vars_delete(key)
27
+ inherited_local_vars_mutex.synchronize do
28
+ raise "Key didn't exist: #{key}" unless _inherited_local_vars.key?(key)
29
+
30
+ _inherited_local_vars.delete(key)
31
+ end
32
+ rescue ThreadError # This can happen when process is closing down
33
+ _inherited_local_vars.delete(key)
34
+ end
35
+
36
+ def self.inherited_local_vars_fetch(key)
37
+ inherited_local_vars_mutex.synchronize do
38
+ return _inherited_local_vars.fetch(key)
39
+ end
40
+ end
41
+
42
+ def self.inherited_local_vars_get(key)
43
+ inherited_local_vars_mutex.synchronize do
44
+ return _inherited_local_vars[key]
45
+ end
46
+ end
47
+
48
+ def self.inherited_local_vars_set(values)
49
+ inherited_local_vars_mutex.synchronize do
50
+ current_vars = _inherited_local_vars
51
+ new_vars = PeakFlowUtils::DeepMerger.execute!(hashes: [current_vars, values])
52
+ Thread.current.instance_variable_set(:@_inherited_local_vars, new_vars)
53
+ end
54
+ end
55
+ end
56
+
57
+ class PeakFlowUtils::InheritedLocalVar
58
+ attr_reader :identifier
59
+
60
+ def self.finalize(inherited_local_var_object_id)
61
+ Thread.inherited_local_vars_delete("inherited_local_var_#{inherited_local_var_object_id}")
62
+ rescue Exception => e # rubocop:disable Lint/RescueException
63
+ puts e.inspect # rubocop:disable Rails/Output
64
+ puts e.backtrace # rubocop:disable Rails/Output
65
+
66
+ raise e
67
+ end
68
+
69
+ def initialize(new_value = nil)
70
+ ObjectSpace.define_finalizer(self, PeakFlowUtils::InheritedLocalVar.method(:finalize))
71
+
72
+ @identifier = "inherited_local_var_#{__id__}"
73
+
74
+ Thread.inherited_local_vars_set(identifier => new_value)
75
+ end
76
+
77
+ def value
78
+ Thread.inherited_local_vars_fetch(identifier)
79
+ end
80
+
81
+ def value=(new_value)
82
+ Thread.inherited_local_vars_set(identifier => new_value)
83
+ end
84
+ end
@@ -1,38 +1,94 @@
1
1
  class PeakFlowUtils::Notifier
2
2
  class FailedToReportError < RuntimeError; end
3
3
  class NotConfiguredError < RuntimeError; end
4
+ class NotifyMessageError < RuntimeError; end
4
5
 
5
- attr_reader :auth_token
6
+ attr_reader :auth_token, :mutex, :parameters
6
7
 
7
8
  def self.configure(auth_token:)
8
9
  @current = PeakFlowUtils::Notifier.new(auth_token: auth_token)
9
10
  end
10
11
 
11
- def self.current
12
- raise PeakFlowUtils::Notifier::NotConfiguredError, "Hasn't been configured" if !@current && Rails.env.test?
13
-
12
+ def self.current # rubocop:disable Style/TrivialAccessors
14
13
  @current
15
14
  end
16
15
 
17
- def self.notify(*args)
18
- PeakFlowUtils::Notifier.current.notify(*args)
16
+ def self.notify(*args, **opts, &blk)
17
+ PeakFlowUtils::Notifier.current&.notify(*args, **opts, &blk)
18
+ end
19
+
20
+ def self.notify_message(*args, **opts, &blk)
21
+ PeakFlowUtils::Notifier.current&.notify_message(*args, **opts, &blk)
22
+ end
23
+
24
+ def self.reset_parameters
25
+ ::PeakFlowUtils::Notifier.current&.instance_variable_set(:@parameters, ::PeakFlowUtils::InheritedLocalVar.new({}))
26
+ end
27
+
28
+ def self.with_parameters(parameters)
29
+ return yield unless ::PeakFlowUtils::Notifier.current
30
+
31
+ random_id = ::SecureRandom.hex(16)
32
+
33
+ ::PeakFlowUtils::Notifier.current.mutex.synchronize do
34
+ raise "'parameters' was nil?" if ::PeakFlowUtils::Notifier.current.parameters.value.nil?
35
+
36
+ parameters_with = ::PeakFlowUtils::Notifier.current.parameters.value.clone
37
+ parameters_with[random_id] = parameters
38
+
39
+ ::PeakFlowUtils::Notifier.current.parameters.value = parameters_with
40
+ end
41
+
42
+ begin
43
+ yield
44
+ ensure
45
+ ::PeakFlowUtils::Notifier.current.mutex.synchronize do
46
+ parameters_without = ::PeakFlowUtils::Notifier.current.parameters.value.clone
47
+ parameters_without.delete(random_id)
48
+
49
+ ::PeakFlowUtils::Notifier.current.parameters.value = parameters_without
50
+ end
51
+ end
19
52
  end
20
53
 
21
54
  def initialize(auth_token:)
22
55
  @auth_token = auth_token
56
+ @mutex = ::Mutex.new
57
+ @parameters = ::PeakFlowUtils::InheritedLocalVar.new({})
58
+ end
59
+
60
+ def current_parameters(parameters: nil)
61
+ hashes = current_parameters_hashes
62
+ hashes << parameters if parameters
63
+
64
+ ::PeakFlowUtils::DeepMerger.execute!(hashes: hashes)
65
+ end
66
+
67
+ def current_parameters_hashes
68
+ parameters.value.values
69
+ end
70
+
71
+ def error_message_from_response(response)
72
+ message = "Couldn't report error to Peakflow (code #{response.code})"
73
+
74
+ if response["content-type"]&.starts_with?("application/json")
75
+ response_data = ::JSON.parse(response.body)
76
+ message << ": #{response_data.fetch("errors").join(". ")}" if response_data["errors"]
77
+ end
78
+
79
+ message
23
80
  end
24
81
 
25
82
  def notify(error:, environment: nil, parameters: nil)
26
- error_parser = PeakFlowUtils::NotifierErrorParser.new(
83
+ error_parser = ::PeakFlowUtils::NotifierErrorParser.new(
27
84
  backtrace: error.backtrace,
28
85
  environment: environment,
29
86
  error: error
30
87
  )
31
88
 
32
- uri = URI("https://www.peakflow.io/errors/reports")
89
+ merged_parameters = current_parameters(parameters: parameters)
33
90
 
34
- https = Net::HTTP.new(uri.host, uri.port)
35
- https.use_ssl = true
91
+ uri = URI("https://www.peakflow.io/errors/reports")
36
92
 
37
93
  data = {
38
94
  auth_token: auth_token,
@@ -43,25 +99,38 @@ class PeakFlowUtils::Notifier
43
99
  file_path: error_parser.file_path,
44
100
  line_number: error_parser.line_number,
45
101
  message: error.message,
46
- parameters: parameters,
102
+ parameters: merged_parameters,
47
103
  remote_ip: error_parser.remote_ip,
48
104
  url: error_parser.url,
49
105
  user_agent: error_parser.user_agent
50
106
  }
51
107
  }
52
108
 
53
- request = Net::HTTP::Post.new(uri.path)
109
+ send_notify_request(data: data, uri: uri)
110
+ end
111
+
112
+ def notify_message(message, **opts)
113
+ raise NotifyMessageError, message
114
+ rescue NotifyMessageError => e
115
+ notify(error: e, **opts)
116
+ end
117
+
118
+ def send_notify_request(data:, uri:)
119
+ https = ::Net::HTTP.new(uri.host, uri.port)
120
+ https.use_ssl = true
121
+
122
+ request = ::Net::HTTP::Post.new(uri.path)
54
123
  request["Content-Type"] = "application/json"
55
- request.body = JSON.generate(data)
124
+ request.body = ::JSON.generate(data)
56
125
 
57
126
  response = https.request(request)
58
127
 
59
128
  raise FailedToReportError, error_message_from_response(response) unless response.code == "200"
60
129
 
61
- response_data = JSON.parse(response.body)
130
+ response_data = ::JSON.parse(response.body)
62
131
 
63
132
  # Data not always present so dont use fetch
64
- PeakFlowUtils::NotifierResponse.new(
133
+ ::PeakFlowUtils::NotifierResponse.new(
65
134
  bug_report_id: response_data["bug_report_id"],
66
135
  bug_report_instance_id: response_data["bug_report_instance_id"],
67
136
  project_id: response_data["project_id"],
@@ -69,15 +138,4 @@ class PeakFlowUtils::Notifier
69
138
  url: response_data["url"]
70
139
  )
71
140
  end
72
-
73
- def error_message_from_response(response)
74
- message = "Couldn't report error to Peakflow (code #{response.code})"
75
-
76
- if response["content-type"]&.starts_with?("application/json")
77
- response_data = JSON.parse(response.body)
78
- message << ": #{response_data.fetch("errors").join(". ")}" if response_data["errors"]
79
- end
80
-
81
- message
82
- end
83
141
  end
@@ -9,13 +9,13 @@ class PeakFlowUtils::NotifierRack
9
9
  rescue Exception => e # rubocop:disable Lint/RescueException
10
10
  controller = env["action_controller.instance"]
11
11
  request = controller&.request
12
- parameters = {}.merge(request.GET).merge(request.POST)
13
12
 
14
- PeakFlowUtils::Notifier.notify(
15
- environment: env,
16
- error: e,
17
- parameters: parameters
18
- )
13
+ PeakFlowUtils::Notifier.with_parameters(rack: {get: request.GET, post: request.POST}) do
14
+ PeakFlowUtils::Notifier.notify(
15
+ environment: env,
16
+ error: e
17
+ )
18
+ end
19
19
 
20
20
  raise e
21
21
  end
@@ -0,0 +1,23 @@
1
+ class Thread
2
+ alias_method :_initialize, :initialize # rubocop:disable Style/Alias
3
+
4
+ def self.on_initialize(&callback)
5
+ @@on_initialize_count = 0 if @on_initialize_count.nil? # rubocop:disable Style/ClassVars
6
+ count_to_use = @@on_initialize_count
7
+ @@on_initialize_count += 1 # rubocop:disable Style/ClassVars
8
+
9
+ @@on_initialize_callbacks ||= {} # rubocop:disable Style/ClassVars
10
+ @@on_initialize_callbacks[count_to_use] = callback
11
+
12
+ count_to_use
13
+ end
14
+
15
+ def initialize(*args, &block)
16
+ @@on_initialize_callbacks ||= {} # rubocop:disable Style/ClassVars
17
+ @@on_initialize_callbacks.each_value do |callback|
18
+ callback.call(parent: Thread.current, thread: self)
19
+ end
20
+
21
+ _initialize(*args, &block)
22
+ end
23
+ end
@@ -1,3 +1,3 @@
1
1
  module PeakFlowUtils
2
- VERSION = "0.1.14".freeze
2
+ VERSION = "0.1.17".freeze
3
3
  end
@@ -6,14 +6,15 @@ require "service_pattern"
6
6
  module PeakFlowUtils
7
7
  path = "#{__dir__}/peak_flow_utils"
8
8
  models_path = "#{__dir__}/peak_flow_utils/models"
9
+ services_path = File.realpath("#{__dir__}/../app/services/peak_flow_utils")
9
10
 
11
+ autoload :InheritedLocalVar, "#{path}/inherited_local_var"
10
12
  autoload :Notifier, "#{path}/notifier"
11
13
  autoload :NotifierErrorParser, "#{path}/notifier_error_parser"
12
14
  autoload :NotifierRack, "#{path}/notifier_rack"
13
15
  autoload :NotifierRails, "#{path}/notifier_rails"
14
16
  autoload :NotifierResponse, "#{path}/notifier_response"
15
17
  autoload :NotifierSidekiq, "#{path}/notifier_sidekiq"
16
- autoload :RspecHelper, "#{path}/rspec_helper"
17
18
  autoload :HandlerHelper, "#{path}/handler_helper"
18
19
 
19
20
  autoload :ApplicationRecord, "#{models_path}/application_record"
@@ -23,4 +24,9 @@ module PeakFlowUtils
23
24
  autoload :ScannedFile, "#{models_path}/scanned_file"
24
25
  autoload :TranslationKey, "#{models_path}/translation_key"
25
26
  autoload :TranslationValue, "#{models_path}/translation_value"
27
+
28
+ autoload :ActiveJobParametersLogging, "#{services_path}/active_job_parameters_logging"
29
+ autoload :ApplicationService, "#{services_path}/application_service"
30
+ autoload :SidekiqParametersLogging, "#{services_path}/sidekiq_parameters_logging"
31
+ autoload :DeepMerger, "#{services_path}/deep_merger"
26
32
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: peak_flow_utils
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.14
4
+ version: 0.1.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - kaspernj
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-04 00:00:00.000000000 Z
11
+ date: 2022-04-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -58,14 +58,42 @@ dependencies:
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 1.0.0
61
+ version: 1.0.5
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: 1.0.0
68
+ version: 1.0.5
69
+ - !ruby/object:Gem::Dependency
70
+ name: appraisal
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry-rails
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
69
97
  - !ruby/object:Gem::Dependency
70
98
  name: redis
71
99
  requirement: !ruby/object:Gem::Requirement
@@ -111,8 +139,7 @@ dependencies:
111
139
  description: Utilities to be used with PeakFlow.
112
140
  email:
113
141
  - kaspernj@gmail.com
114
- executables:
115
- - peak_flow_rspec_files
142
+ executables: []
116
143
  extensions: []
117
144
  extra_rdoc_files: []
118
145
  files:
@@ -130,24 +157,27 @@ files:
130
157
  - app/handlers/peak_flow_utils/simple_form_handler.rb
131
158
  - app/handlers/peak_flow_utils/validations_handler.rb
132
159
  - app/handlers/peak_flow_utils/will_paginate_handler.rb
160
+ - app/services/peak_flow_utils/active_job_parameters_logging.rb
133
161
  - app/services/peak_flow_utils/application_migration.rb
134
162
  - app/services/peak_flow_utils/application_service.rb
135
163
  - app/services/peak_flow_utils/attribute_service.rb
136
164
  - app/services/peak_flow_utils/configuration_service.rb
137
165
  - app/services/peak_flow_utils/database_initializer_service.rb
166
+ - app/services/peak_flow_utils/deep_merger.rb
138
167
  - app/services/peak_flow_utils/erb_inspector.rb
139
168
  - app/services/peak_flow_utils/erb_inspector/file_inspector.rb
140
169
  - app/services/peak_flow_utils/erb_inspector/translation_inspector.rb
141
170
  - app/services/peak_flow_utils/group_service.rb
142
171
  - app/services/peak_flow_utils/handlers_finder_service.rb
143
172
  - app/services/peak_flow_utils/model_inspector.rb
173
+ - app/services/peak_flow_utils/sidekiq_parameters_logging.rb
144
174
  - app/services/peak_flow_utils/translation_service.rb
145
175
  - app/services/peak_flow_utils/translations_parser_service.rb
146
- - bin/peak_flow_rspec_files
147
176
  - config/routes.rb
148
177
  - lib/peak_flow_utils.rb
149
178
  - lib/peak_flow_utils/engine.rb
150
179
  - lib/peak_flow_utils/handler_helper.rb
180
+ - lib/peak_flow_utils/inherited_local_var.rb
151
181
  - lib/peak_flow_utils/migrations/20150902155200_create_translation_keys.rb
152
182
  - lib/peak_flow_utils/migrations/20150907070908_create_handlers.rb
153
183
  - lib/peak_flow_utils/migrations/20150907070909_create_groups.rb
@@ -167,7 +197,7 @@ files:
167
197
  - lib/peak_flow_utils/notifier_rails.rb
168
198
  - lib/peak_flow_utils/notifier_response.rb
169
199
  - lib/peak_flow_utils/notifier_sidekiq.rb
170
- - lib/peak_flow_utils/rspec_helper.rb
200
+ - lib/peak_flow_utils/thread_callbacks_patch.rb
171
201
  - lib/peak_flow_utils/version.rb
172
202
  - lib/tasks/peak_flow_utils_tasks.rake
173
203
  homepage: https://github.com/kaspernj/peak_flow_utils
@@ -189,7 +219,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
189
219
  - !ruby/object:Gem::Version
190
220
  version: '0'
191
221
  requirements: []
192
- rubygems_version: 3.1.6
222
+ rubygems_version: 3.2.32
193
223
  signing_key:
194
224
  specification_version: 4
195
225
  summary: Utilities to be used with PeakFlow.
@@ -1,21 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- # This task detects and prints out the RSpec files for the current build group
4
-
5
- require "#{__dir__}/../lib/peak_flow_utils"
6
-
7
- args = {}
8
- ARGV.each do |arg|
9
- if (match = arg.match(/\A--(.+?)=(.+)\Z/))
10
- args[match[1]] = match[2]
11
- end
12
- end
13
-
14
- rspec_helper = PeakFlowUtils::RspecHelper.new(
15
- groups: args.fetch("groups").to_i,
16
- group_number: args.fetch("group-number").to_i,
17
- only_types: args["only-types"]&.split(","),
18
- tags: args["tags"]&.split(",")
19
- )
20
-
21
- print rspec_helper.group_files.map { |group_file| group_file.fetch(:path) }.join(" ")
@@ -1,209 +0,0 @@
1
- class PeakFlowUtils::RspecHelper
2
- attr_reader :only_types, :tags
3
-
4
- def initialize(groups:, group_number:, only_types: nil, tags: nil)
5
- @groups = groups
6
- @group_number = group_number
7
- @example_data_exists = File.exist?("spec/examples.txt")
8
- @only_types = only_types
9
- @tags = tags
10
- end
11
-
12
- def example_data_exists?
13
- @example_data_exists
14
- end
15
-
16
- def example_data
17
- @example_data ||= begin
18
- raw_data = File.read("spec/examples.txt")
19
-
20
- result = []
21
- raw_data.scan(/^\.\/(.+)\[(.+?)\]\s+\|\s+(.+?)\s+\|\s+((.+?) seconds|)\s+\|$/) do |match|
22
- file_path = match[0]
23
- spec_result = match[1]
24
- seconds = match[4]&.to_f
25
-
26
- spec_data = {
27
- file_path: file_path,
28
- spec_result: spec_result,
29
- seconds: seconds
30
- }
31
-
32
- result << spec_data
33
- end
34
-
35
- result
36
- end
37
- end
38
-
39
- def example_files
40
- @example_files ||= begin
41
- files = {}
42
- example_data.each do |spec_data|
43
- file_path = spec_data.fetch(:file_path)
44
- seconds = spec_data.fetch(:seconds)
45
-
46
- files[file_path] ||= {examples: 0, seconds: 0.0}
47
- files[file_path][:examples] += 1
48
- files[file_path][:seconds] += seconds if seconds
49
- end
50
-
51
- files
52
- end
53
- end
54
-
55
- def example_file(path)
56
- example_files[path]
57
- end
58
-
59
- def group_files
60
- return @group_files if @group_files
61
-
62
- sorted_files.each do |file|
63
- file_path = file.fetch(:path)
64
- file_data = example_file(file_path) if example_data_exists?
65
-
66
- if file_data
67
- examples = file_data.fetch(:examples)
68
- seconds = file_data.fetch(:seconds)
69
- else
70
- examples = file.fetch(:examples)
71
- end
72
-
73
- group = group_with_least
74
- group[:examples] += examples
75
- group[:files] << file
76
- group[:seconds] += seconds if seconds
77
- end
78
-
79
- @group_files = group_orders[@group_number - 1].fetch(:files)
80
- end
81
-
82
- def group_orders
83
- @group_orders ||= begin
84
- group_orders = []
85
- @groups.times do
86
- group_orders << {
87
- examples: 0,
88
- files: [],
89
- seconds: 0.0
90
- }
91
- end
92
- group_orders
93
- end
94
- end
95
-
96
- def group_with_least
97
- group_orders.min do |group1, group2|
98
- if example_data_exists? && group1.fetch(:seconds) != 0.0 && group2.fetch(:seconds) != 0.0
99
- group1.fetch(:seconds) <=> group2.fetch(:seconds)
100
- else
101
- group1.fetch(:examples) <=> group2.fetch(:examples)
102
- end
103
- end
104
- end
105
-
106
- # Sort them so that they are sorted by file path in three groups so each group have an equal amount of controller specs, features specs and so on
107
- def sorted_files
108
- files.values.sort do |file1, file2|
109
- file1_path = file1.fetch(:path)
110
- file2_path = file2.fetch(:path)
111
-
112
- file1_data = example_file(file1_path) if example_data_exists?
113
- file2_data = example_file(file2_path) if example_data_exists?
114
-
115
- if file1_data && file2_data && file1_data.fetch(:seconds) != 0.0 && file2_data.fetch(:seconds) != 0.0
116
- value1 = file1_data[:seconds]
117
- else
118
- value1 = file1.fetch(:points)
119
- end
120
-
121
- if file2_data && file1_data && file2_data.fetch(:seconds) != 0.0 && file2_data.fetch(:seconds) != 0.0
122
- value2 = file2_data[:seconds]
123
- else
124
- value2 = file2.fetch(:points)
125
- end
126
-
127
- if value1 == value2
128
- value2 = file1_path
129
- value1 = file2_path
130
- end
131
-
132
- value2 <=> value1
133
- end
134
- end
135
-
136
- private
137
-
138
- def dry_result
139
- @dry_result ||= begin
140
- require "json"
141
- require "rspec/core"
142
-
143
- output_capture = StringIO.new
144
- RSpec::Core::Runner.run(rspec_options, $stderr, output_capture)
145
-
146
- result = ::JSON.parse(output_capture.string)
147
-
148
- raise "No examples were found" if result.fetch("examples").empty?
149
-
150
- result
151
- end
152
- end
153
-
154
- def dry_file(path)
155
- files.fetch(path)
156
- end
157
-
158
- def files
159
- @files ||= begin
160
- result = {}
161
- dry_result.fetch("examples").each do |example|
162
- file_path = example.fetch("file_path")
163
- file_path = file_path[2, file_path.length]
164
- type = type_from_path(file_path)
165
- points = points_from_type(type)
166
-
167
- next if ignore_type?(type)
168
-
169
- result[file_path] = {examples: 0, path: file_path, points: 0, type: type} unless result.key?(file_path)
170
- result[file_path][:examples] += 1
171
- result[file_path][:points] += points
172
- end
173
-
174
- result
175
- end
176
- end
177
-
178
- def ignore_type?(type)
179
- only_types && !only_types.include?(type) # rubocop:disable Rails/NegateInclude:, Style/SafeNavigation
180
- end
181
-
182
- def points_from_type(type)
183
- if type == "feature" || type == "system"
184
- 10
185
- elsif type == "controllers"
186
- 3
187
- else
188
- 1
189
- end
190
- end
191
-
192
- def rspec_options
193
- rspec_options = ["--dry-run", "--format", "json"]
194
-
195
- tags&.each do |tag|
196
- rspec_options += ["--tag", tag]
197
- end
198
-
199
- # Add the folder with all the specs, which is required when running programmatically
200
- rspec_options << "spec"
201
-
202
- rspec_options
203
- end
204
-
205
- def type_from_path(file_path)
206
- match = file_path.match(/^spec\/(.+?)\//)
207
- match[1] if match
208
- end
209
- end