peak_flow_utils 0.1.14 → 0.1.17

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