appmap 0.99.4 → 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: eda0d23ff548aaa0e6e61eef0e7734a542660eeeb45e899f54a7287665baaf7e
4
- data.tar.gz: 84dc9f162eeae76f58f0c6015ec47686509aabf9ac4cac727016957b69421daf
3
+ metadata.gz: e4d9d1069381257dbf69ca6becafb2bfbbc948039b89060aeb607f73b8992392
4
+ data.tar.gz: da3124df4bc13ca45b0ed5c67f57667cf5c9d2773afcd4f5f9bcee4b0bf8aa0a
5
5
  SHA512:
6
- metadata.gz: af594c4d511623a7e9b37bc3bc45d5d2460de1410e9177f544e900c1d1cd5a555be04bef870c8c5e18a05f46fb242afd6dd9ce26646fc700f071c7a4d917da5a
7
- data.tar.gz: 49a5e81dd6f504c1e5998f8dc2af53e1b657ddcef387005cd6f313f6c299eb46cc27b7a1c470356de8170435e459d3b1f5e06ef65a1d72f92cea5c0eed797607
6
+ metadata.gz: 854833429a403e7ae83cfd8eabe76228d273787713aa4d44a829746264044e2a1ab8b4b7620dbc1ba36043c6c6b5128f223d38231ff281d6c48dd1b900cde800
7
+ data.tar.gz: f907c847bb4a20a57b6ad0172e289d45ddb08162d526470ae3ee05c3d2f0988b7a820c47335d65c1f399b15cc2a611aeb30e0660a2b0c24ef8f2f880ecc13df2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
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
+
13
+ # [0.100.0](https://github.com/getappmap/appmap-ruby/compare/v0.99.4...v0.100.0) (2023-07-11)
14
+
15
+
16
+ ### Features
17
+
18
+ * Add `appmap-agent-config` command to bootstrap appmap.yml ([568acc3](https://github.com/getappmap/appmap-ruby/commit/568acc3948871f736ba72244a04fd54d8be67615))
19
+
1
20
  ## [0.99.4](https://github.com/getappmap/appmap-ruby/compare/v0.99.3...v0.99.4) (2023-05-15)
2
21
 
3
22
 
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'optparse'
5
+ require 'appmap'
6
+ require 'appmap/command/agent_setup/config'
7
+
8
+ @options = { config_file: AppMap::DEFAULT_CONFIG_FILE_PATH, force: false }
9
+
10
+ OptionParser.new do |parser|
11
+ parser.banner = 'Usage: appmap-agent-config [options]'
12
+
13
+ description = "AppMap configuration file path (default: #{AppMap::DEFAULT_CONFIG_FILE_PATH})"
14
+ parser.on('-c', '--config=FILEPATH', description) do |filepath|
15
+ @options[:config_file] = filepath
16
+ end
17
+
18
+ parser.on('-f', '--force', 'Overwrite existing configuration file') do
19
+ @options[:force] = true
20
+ end
21
+ end.parse!
22
+
23
+ begin
24
+ AppMap::Command::AgentSetup::Config.new(@options[:config_file], @options[:force]).perform
25
+
26
+ puts "AppMap configuration file created at #{@options[:config_file]}"
27
+ rescue AppMap::Command::AgentSetup::Config::FileExistsError
28
+ puts "AppMap configuration file already exists at #{@options[:config_file]}"
29
+ puts 'Use the --force option to overwrite.'
30
+ exit 1
31
+ end
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
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'appmap/service/guesser'
5
+
6
+ module AppMap
7
+ module Command
8
+ module AgentSetup
9
+ ConfigStruct = Struct.new(:config_file, :overwrite)
10
+
11
+ class Config < ConfigStruct
12
+ class FileExistsError < StandardError; end
13
+
14
+ def perform
15
+ raise FileExistsError unless overwrite || !File.exist?(config_file)
16
+
17
+ config = {
18
+ 'name' => Service::Guesser.guess_name,
19
+ 'packages' => Service::Guesser.guess_paths.map { |path| { 'path' => path } },
20
+ 'language' => 'ruby',
21
+ 'appmap_dir' => 'tmp/appmap'
22
+ }
23
+
24
+ File.write(config_file, YAML.dump(config))
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
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.99.4'
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.99.4
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-05-15 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
@@ -308,6 +308,7 @@ description:
308
308
  email:
309
309
  - kgilpin@gmail.com
310
310
  executables:
311
+ - appmap-agent-config
311
312
  - appmap-agent-init
312
313
  - appmap-agent-status
313
314
  - appmap-agent-validate
@@ -347,6 +348,7 @@ files:
347
348
  - examples/mock_webapp/lib/mock_webapp/controller.rb
348
349
  - examples/mock_webapp/lib/mock_webapp/request.rb
349
350
  - examples/mock_webapp/lib/mock_webapp/user.rb
351
+ - exe/appmap-agent-config
350
352
  - exe/appmap-agent-init
351
353
  - exe/appmap-agent-status
352
354
  - exe/appmap-agent-validate
@@ -365,6 +367,7 @@ files:
365
367
  - lib/appmap/builtin_hooks/psych.yml
366
368
  - lib/appmap/builtin_hooks/ruby.yml
367
369
  - lib/appmap/class_map.rb
370
+ - lib/appmap/command/agent_setup/config.rb
368
371
  - lib/appmap/command/agent_setup/init.rb
369
372
  - lib/appmap/command/agent_setup/status.rb
370
373
  - lib/appmap/command/agent_setup/validate.rb
@@ -422,6 +425,7 @@ files:
422
425
  - lib/appmap/record.rb
423
426
  - lib/appmap/recording_methods.rb
424
427
  - lib/appmap/rspec.rb
428
+ - lib/appmap/rswag.rb
425
429
  - lib/appmap/service/config_analyzer.rb
426
430
  - lib/appmap/service/guesser.rb
427
431
  - lib/appmap/service/integration_test_path_finder.rb