appmap 0.100.0 → 0.101.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f52fa6b2d5e7414553f16fbcf018b4d32214bdfcda4afd2567d9bef11203979
4
- data.tar.gz: 0d456c7e95dc01ae731bbe9def89ba1585651412d6d8a35eaa2f9c4f83580d2f
3
+ metadata.gz: e4d9d1069381257dbf69ca6becafb2bfbbc948039b89060aeb607f73b8992392
4
+ data.tar.gz: da3124df4bc13ca45b0ed5c67f57667cf5c9d2773afcd4f5f9bcee4b0bf8aa0a
5
5
  SHA512:
6
- metadata.gz: a07b7341627a96c8ef21930d21a095e9cf849cf06b882d980ec670ce07003dc28e68d2a30e930fb203bf4315ca7aba1f60d43e5d7053d2e56e598ed7d6709302
7
- data.tar.gz: 691c992e647a2760de0ae0ec75c00efb6824b008abd85f6506c31d34175e52d178d5e3297ab3414eb918fc3e083bba208b0b04c9c74f763330bc4333eb078ce1
6
+ metadata.gz: 854833429a403e7ae83cfd8eabe76228d273787713aa4d44a829746264044e2a1ab8b4b7620dbc1ba36043c6c6b5128f223d38231ff281d6c48dd1b900cde800
7
+ data.tar.gz: f907c847bb4a20a57b6ad0172e289d45ddb08162d526470ae3ee05c3d2f0988b7a820c47335d65c1f399b15cc2a611aeb30e0660a2b0c24ef8f2f880ecc13df2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ # [0.101.0](https://github.com/getappmap/appmap-ruby/compare/v0.100.0...v0.101.0) (2023-07-17)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Auto-enable tracing on block recording ([59886b6](https://github.com/getappmap/appmap-ruby/commit/59886b6b4720240e9f961a8b76f3035eaf3be44e))
7
+
8
+
9
+ ### Features
10
+
11
+ * Record RSwag ([a60d480](https://github.com/getappmap/appmap-ruby/commit/a60d4803feeab519ef4eae1702da00e3dc2dd4ea))
12
+
1
13
  # [0.100.0](https://github.com/getappmap/appmap-ruby/compare/v0.99.4...v0.100.0) (2023-07-11)
2
14
 
3
15
 
data/lib/appmap/agent.rb CHANGED
@@ -73,6 +73,7 @@ module AppMap
73
73
  # and returns an AppMap as a Hash.
74
74
  def record
75
75
  tracer = tracing.trace
76
+ tracer.enable
76
77
  begin
77
78
  yield
78
79
  ensure
data/lib/appmap/rspec.rb CHANGED
@@ -68,7 +68,7 @@ module AppMap
68
68
  example_group.parent_groups.first
69
69
  end
70
70
 
71
- example_group_parent != example_group ? ScopeExampleGroup.new(example_group_parent) : nil
71
+ example_group_parent == example_group ? nil : ScopeExampleGroup.new(example_group_parent)
72
72
  end
73
73
  end
74
74
 
@@ -87,7 +87,7 @@ module AppMap
87
87
 
88
88
  warn "Starting recording of example #{example}@#{source_location}" if AppMap::RSpec::LOG
89
89
  @trace = AppMap.tracing.trace
90
- @webdriver_port = webdriver_port.()
90
+ @webdriver_port = webdriver_port.call
91
91
  end
92
92
 
93
93
  def source_location
@@ -147,7 +147,7 @@ module AppMap
147
147
  end
148
148
  end
149
149
 
150
- @recordings_by_example = {}
150
+ @recordings_by_example = {}.tap(&:compare_by_identity)
151
151
  @event_methods = Set.new
152
152
  @recording_count = 0
153
153
 
@@ -161,34 +161,36 @@ module AppMap
161
161
  end
162
162
 
163
163
  def begin_spec(example)
164
- AppMap.info 'Configuring AppMap recorder for RSpec' if first_recording?
165
164
  @recording_count += 1
166
-
167
- recording = if example.metadata[:appmap] != false
168
- Recording.new(example)
169
- else
170
- :false
171
- end
172
-
173
- @recordings_by_example[example.object_id] = recording
165
+ # Disable RSpec recording for RSwag, because all the action happens in the before block.
166
+ # The example is empty except for assertions. So RSwag has its own recorder, and RSpec
167
+ # recording is disabled so it won't clobber the AppMap with empty data.
168
+ recording = if example.metadata[:appmap] == false || example.metadata[:rswag]
169
+ :disabled
170
+ else
171
+ Recording.new(example)
172
+ end
173
+
174
+ @recordings_by_example[example] = recording
174
175
  end
175
176
 
176
177
  def end_spec(example, exception:)
177
- recording = @recordings_by_example.delete(example.object_id)
178
+ recording = @recordings_by_example.delete(example)
178
179
  return warn "No recording found for #{example}" unless recording
179
180
 
180
- recording.finish example.execution_result.exception || exception, exception unless recording == :false
181
+ recording.finish example.execution_result.exception || exception, exception unless recording == :disabled
181
182
  end
182
183
 
183
184
  def config
184
- @config or raise "AppMap is not configured"
185
+ @config or raise 'AppMap is not configured'
185
186
  end
186
187
 
187
188
  def add_event_methods(event_methods)
188
189
  @event_methods += event_methods
189
190
  end
190
191
 
191
- def save(name:, class_map:, source_location:, test_status:, test_failure:, exception:, events:)
192
+ def save(name:, class_map:, source_location:, test_status:, test_failure:, exception:, events:, frameworks: [],
193
+ recorder: nil)
192
194
  metadata = AppMap::RSpec.metadata.tap do |m|
193
195
  m[:name] = name
194
196
  m[:source_location] = source_location
@@ -198,7 +200,8 @@ module AppMap
198
200
  name: 'rspec',
199
201
  version: Gem.loaded_specs['rspec-core']&.version&.to_s
200
202
  }
201
- m[:recorder] = {
203
+ m[:frameworks] += frameworks
204
+ m[:recorder] = recorder || {
202
205
  name: 'rspec',
203
206
  type: 'tests'
204
207
  }
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'appmap'
4
+
5
+ module AppMap
6
+ module Rswag
7
+ def self.record(metadata, &block)
8
+ description = metadata[:full_description]
9
+ warn "Recording of RSwag test #{description}" if AppMap::RSpec::LOG
10
+ source_location = (metadata[:example_group] || {})[:location]
11
+
12
+ appmap = AppMap.record(&block)
13
+
14
+ events = appmap['events']
15
+ class_map = appmap['classMap']
16
+
17
+ exception = (events.last || {})[:exception]
18
+ failed = true if exception
19
+ warn "Finishing recording of #{failed ? 'failed ' : ''} RSwag test #{description}" if AppMap::RSpec::LOG
20
+ warn "Exception: #{exception}" if exception && AppMap::RSpec::LOG
21
+
22
+ if failed
23
+ warn "Failure exception: #{exception}" if AppMap::RSpec::LOG
24
+ test_failure = Util.extract_test_failure(exception)
25
+ end
26
+
27
+ AppMap::RSpec.save name: description,
28
+ class_map: class_map,
29
+ source_location: source_location,
30
+ test_status: exception ? 'failed' : 'succeeded',
31
+ test_failure: test_failure,
32
+ exception: exception,
33
+ events: events,
34
+ frameworks: [
35
+ { name: 'rswag',
36
+ version: Gem.loaded_specs['rswag-specs']&.version&.to_s }
37
+ ],
38
+ recorder: {
39
+ name: 'rswag',
40
+ type: 'tests'
41
+ }
42
+ end
43
+
44
+ class << self
45
+ def enabled?
46
+ RSpec.enabled? && defined?(Rswag)
47
+ end
48
+ end
49
+ end
50
+
51
+ if Rswag.enabled?
52
+ require 'rswag'
53
+ require 'rswag/specs'
54
+ require 'appmap/rspec'
55
+
56
+ module ::Rswag
57
+ module Specs
58
+ module ExampleHelpers
59
+ alias submit_request_without_appmap submit_request
60
+
61
+ def submit_request(metadata)
62
+ AppMap::Rswag.record(metadata) do
63
+ submit_request_without_appmap(metadata)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
data/lib/appmap/trace.rb CHANGED
@@ -24,7 +24,7 @@ module AppMap
24
24
  return nil if source_location.nil? || source_location.first.start_with?('<')
25
25
 
26
26
  # Do not use method_source's comment method because it's slow
27
- @comment ||= RubyMethod.last_comment *source_location
27
+ @comment ||= RubyMethod.last_comment(*source_location)
28
28
  rescue Errno::EINVAL, Errno::ENOENT
29
29
  nil
30
30
  end
@@ -41,8 +41,6 @@ module AppMap
41
41
  @package.labels
42
42
  end
43
43
 
44
- private
45
-
46
44
  # Read file and get last comment before line.
47
45
  def self.last_comment(file, line_number)
48
46
  File.open(file) do |f|
@@ -114,19 +112,19 @@ module AppMap
114
112
  end
115
113
 
116
114
  def initialize
117
- @@stacks ||= Hash.new
115
+ @@stacks ||= {}
118
116
  end
119
117
 
120
118
  def record(event)
121
119
  stack = caller.select { |line| !line.index('/lib/appmap/') }[0...StackPrinter.depth].join("\n ")
122
120
  stack_hash = Digest::SHA256.hexdigest(stack)
123
- unless @@stacks[stack_hash]
124
- @@stacks[stack_hash] = stack
125
- puts
126
- puts 'Event: ' + event.to_h.map { |k, v| [ "#{k}: #{v}" ] }.join(", ")
127
- puts ' ' + stack
128
- puts
129
- end
121
+ return if @@stacks[stack_hash]
122
+
123
+ @@stacks[stack_hash] = stack
124
+ puts
125
+ puts 'Event: ' + event.to_h.map { |k, v| [ "#{k}: #{v}" ] }.join(', ')
126
+ puts ' ' + stack
127
+ puts
130
128
  end
131
129
  end
132
130
 
@@ -163,7 +161,9 @@ module AppMap
163
161
  def record_event(event, package: nil, defined_class: nil, method: nil)
164
162
  return unless @enabled
165
163
 
166
- raise "Expected event in thread #{@thread_id}, got #{event.thread_id}" if @thread_id && @thread_id != event.thread_id
164
+ if @thread_id && @thread_id != event.thread_id
165
+ raise "Expected event in thread #{@thread_id}, got #{event.thread_id}"
166
+ end
167
167
 
168
168
  @stack_printer.record(event) if @stack_printer
169
169
  @last_package_for_thread[Thread.current.object_id] = package if package
data/lib/appmap/util.rb CHANGED
@@ -25,9 +25,17 @@ module AppMap
25
25
  package_tokens = name.split('/')
26
26
 
27
27
  class_and_name = package_tokens.pop
28
- class_name, function_name, static = class_and_name.include?('.') ? class_and_name.split('.', 2) + [ true ] : class_and_name.split('#', 2) + [ false ]
28
+ class_name, function_name, static = if class_and_name.include?('.')
29
+ class_and_name.split('.',
30
+ 2) + [ true ]
31
+ else
32
+ class_and_name.split(
33
+ '#', 2
34
+ ) + [ false ]
35
+ end
29
36
 
30
37
  raise "Malformed fully-qualified function name #{name}" unless function_name
38
+
31
39
  [ package_tokens.empty? ? 'ruby' : package_tokens.join('/'), class_name, static, function_name ]
32
40
  end
33
41
 
@@ -70,20 +78,20 @@ module AppMap
70
78
  def sanitize_paths(h)
71
79
  require 'hashie'
72
80
  h.extend(Hashie::Extensions::DeepLocate)
73
- keys = %i(path location)
74
- h.deep_locate ->(k,v,o) {
81
+ keys = %i[path location]
82
+ h.deep_locate lambda { |k, _v, o|
75
83
  next unless keys.include?(k)
76
-
77
- fix = ->(v) {v.gsub(%r{#{Gem.dir}/gems/.*(?=lib)}, '')}
78
- keys.each {|k| o[k] = fix.(o[k]) if o[k] }
84
+
85
+ fix = ->(v) { v.gsub(%r{#{Gem.dir}/gems/.*(?=lib)}, '') }
86
+ keys.each { |k| o[k] = fix.call(o[k]) if o[k] }
79
87
  }
80
88
 
81
89
  h
82
90
  end
83
-
91
+
84
92
  # sanitize_event removes ephemeral values from an event, making
85
93
  # events easier to compare across runs.
86
- def sanitize_event(event, &block)
94
+ def sanitize_event(event)
87
95
  event.delete(:thread_id)
88
96
  event.delete(:elapsed)
89
97
  event.delete(:elapsed_instrumentation)
@@ -114,8 +122,8 @@ module AppMap
114
122
  blank?(headers) ? nil : headers
115
123
  end
116
124
 
117
- if !env['rack.version']
118
- warn "Request headers does not contain rack.version. HTTP_ prefix is not expected."
125
+ unless env['rack.version']
126
+ warn 'Request headers does not contain rack.version. HTTP_ prefix is not expected.'
119
127
  return finalize_headers.call(env.dup)
120
128
  end
121
129
 
@@ -123,18 +131,18 @@ module AppMap
123
131
  # Apparently, it's following the CGI spec in doing so.
124
132
  # https://datatracker.ietf.org/doc/html/rfc3875#section-4.1.18
125
133
  matching_headers = env
126
- .select { |k,v| k.to_s.start_with? 'HTTP_' }
127
- .merge(
128
- 'CONTENT_TYPE' => env['CONTENT_TYPE'],
129
- 'CONTENT_LENGTH' => env['CONTENT_LENGTH'],
130
- 'AUTHORIZATION' => env['AUTHORIZATION']
131
- )
132
- .reject { |k,v| blank?(v) }
133
- .each_with_object({}) do |kv, memo|
134
- key = kv[0].sub(/^HTTP_/, '').split('_').map(&:capitalize).join('-')
135
- value = kv[1]
136
- memo[key] = value
137
- end
134
+ .select { |k, _v| k.to_s.start_with? 'HTTP_' }
135
+ .merge(
136
+ 'CONTENT_TYPE' => env['CONTENT_TYPE'],
137
+ 'CONTENT_LENGTH' => env['CONTENT_LENGTH'],
138
+ 'AUTHORIZATION' => env['AUTHORIZATION']
139
+ )
140
+ .reject { |_k, v| blank?(v) }
141
+ .each_with_object({}) do |kv, memo|
142
+ key = kv[0].sub(/^HTTP_/, '').split('_').map(&:capitalize).join('-')
143
+ value = kv[1]
144
+ memo[key] = value
145
+ end
138
146
  finalize_headers.call(matching_headers)
139
147
  end
140
148
 
@@ -143,7 +151,7 @@ module AppMap
143
151
  is_bundled_path = -> { path.index(Bundler.bundle_path.to_s) == 0 }
144
152
  is_vendored_path = -> { path.index(File.join(Dir.pwd, 'vendor/bundle')) == 0 }
145
153
 
146
- if is_local_path.() && !is_bundled_path.() && !is_vendored_path.()
154
+ if is_local_path.call && !is_bundled_path.call && !is_vendored_path.call
147
155
  path[Dir.pwd.length + 1..-1]
148
156
  else
149
157
  path
@@ -153,7 +161,7 @@ module AppMap
153
161
  def format_exception(exception)
154
162
  {
155
163
  class: exception.class.name,
156
- message: AppMap::Event::MethodEvent.display_string(exception.to_s),
164
+ message: AppMap::Event::MethodEvent.display_string(exception.to_s)
157
165
  }
158
166
  end
159
167
 
@@ -164,7 +172,10 @@ module AppMap
164
172
  first_location = exception.backtrace_locations&.find do |location|
165
173
  !Pathname.new(normalize_path(location.absolute_path || location.path)).absolute?
166
174
  end
167
- test_failure[:location] = [ normalize_path(first_location.path), first_location.lineno ].join(':') if first_location
175
+ if first_location
176
+ test_failure[:location] =
177
+ [ normalize_path(first_location.path), first_location.lineno ].join(':')
178
+ end
168
179
  end
169
180
  end
170
181
 
@@ -175,7 +186,7 @@ module AppMap
175
186
  tokens = path.split('/')
176
187
  tokens.map do |token|
177
188
  # stop matching if an ending ) is found
178
- token.gsub(/^:(.*[^\)])/, '{\1}')
189
+ token.gsub(/^:(.*[^)])/, '{\1}')
179
190
  end.join('/')
180
191
  end
181
192
 
@@ -196,22 +207,23 @@ module AppMap
196
207
 
197
208
  def color(text, color, bold: false)
198
209
  color = Util.const_get(color.to_s.upcase) if color.is_a?(Symbol)
199
- bold = bold ? BOLD : ""
210
+ bold = bold ? BOLD : ''
200
211
  "#{bold}#{color}#{text}#{CLEAR}"
201
212
  end
202
213
 
203
214
  def classify(word)
204
- word.split(/[\-_]/).map(&:capitalize).join
215
+ word.split(/[-_]/).map(&:capitalize).join
205
216
  end
206
217
 
207
218
  # https://api.rubyonrails.org/v6.1.3.2/classes/ActiveSupport/Inflector.html#method-i-underscore
208
219
  def underscore(camel_cased_word)
209
220
  return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word)
210
- word = camel_cased_word.to_s.gsub("::", "/")
221
+
222
+ word = camel_cased_word.to_s.gsub('::', '/')
211
223
  # word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_' }#{$2.downcase}" }
212
224
  word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
213
225
  word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
214
- word.tr!("-", "_")
226
+ word.tr!('-', '_')
215
227
  word.downcase!
216
228
  word
217
229
  end
@@ -223,7 +235,7 @@ module AppMap
223
235
 
224
236
  def blank?(obj)
225
237
  return true if obj.nil?
226
-
238
+
227
239
  return true if obj.is_a?(String) && obj == ''
228
240
 
229
241
  return true if obj.respond_to?(:length) && obj.length == 0
@@ -3,7 +3,7 @@
3
3
  module AppMap
4
4
  URL = 'https://github.com/applandinc/appmap-ruby'
5
5
 
6
- VERSION = '0.100.0'
6
+ VERSION = '0.101.0'
7
7
 
8
8
  APPMAP_FORMAT_VERSION = '1.12.0'
9
9
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appmap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.100.0
4
+ version: 0.101.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Gilpin
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-07-11 00:00:00.000000000 Z
11
+ date: 2023-07-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: method_source
@@ -425,6 +425,7 @@ files:
425
425
  - lib/appmap/record.rb
426
426
  - lib/appmap/recording_methods.rb
427
427
  - lib/appmap/rspec.rb
428
+ - lib/appmap/rswag.rb
428
429
  - lib/appmap/service/config_analyzer.rb
429
430
  - lib/appmap/service/guesser.rb
430
431
  - lib/appmap/service/integration_test_path_finder.rb