appmap 0.100.0 → 0.101.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: 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