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 +4 -4
- data/CHANGELOG.md +12 -0
- data/lib/appmap/agent.rb +1 -0
- data/lib/appmap/rspec.rb +20 -17
- data/lib/appmap/rswag.rb +70 -0
- data/lib/appmap/trace.rb +12 -12
- data/lib/appmap/util.rb +43 -31
- data/lib/appmap/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e4d9d1069381257dbf69ca6becafb2bfbbc948039b89060aeb607f73b8992392
|
|
4
|
+
data.tar.gz: da3124df4bc13ca45b0ed5c67f57667cf5c9d2773afcd4f5f9bcee4b0bf8aa0a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
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
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
|
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 == :
|
|
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
|
|
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[:
|
|
203
|
+
m[:frameworks] += frameworks
|
|
204
|
+
m[:recorder] = recorder || {
|
|
202
205
|
name: 'rspec',
|
|
203
206
|
type: 'tests'
|
|
204
207
|
}
|
data/lib/appmap/rswag.rb
ADDED
|
@@ -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
|
|
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 ||=
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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?('.')
|
|
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
|
|
74
|
-
h.deep_locate
|
|
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
|
|
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
|
-
|
|
118
|
-
warn
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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.
|
|
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
|
-
|
|
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(/^:(.*[
|
|
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(/[
|
|
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
|
-
|
|
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
|
data/lib/appmap/version.rb
CHANGED
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.
|
|
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
|
+
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
|