appmap 0.52.1 → 0.54.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +16 -10
  3. data/CHANGELOG.md +41 -0
  4. data/Rakefile +13 -4
  5. data/appmap.gemspec +1 -1
  6. data/exe/appmap-agent-init +19 -0
  7. data/lib/appmap.rb +31 -112
  8. data/lib/appmap/agent.rb +115 -0
  9. data/lib/appmap/class_map.rb +2 -2
  10. data/lib/appmap/config.rb +11 -3
  11. data/lib/appmap/handler/net_http.rb +3 -2
  12. data/lib/appmap/handler/rails/request_handler.rb +2 -1
  13. data/lib/appmap/handler/rails/template.rb +2 -1
  14. data/lib/appmap/hook.rb +0 -1
  15. data/lib/appmap/hook/method.rb +36 -28
  16. data/lib/appmap/metadata.rb +4 -2
  17. data/lib/appmap/minitest.rb +2 -0
  18. data/lib/appmap/railtie.rb +2 -2
  19. data/lib/appmap/rspec.rb +5 -3
  20. data/lib/appmap/swagger.rb +2 -0
  21. data/lib/appmap/swagger/configuration.rb +70 -0
  22. data/lib/appmap/swagger/markdown_descriptions.rb +43 -0
  23. data/lib/appmap/swagger/rake_tasks.rb +43 -0
  24. data/lib/appmap/swagger/stable.rb +37 -0
  25. data/lib/appmap/trace.rb +2 -0
  26. data/lib/appmap/util.rb +17 -1
  27. data/lib/appmap/version.rb +1 -1
  28. data/package.json +0 -1
  29. data/release.sh +0 -1
  30. data/spec/config_spec.rb +0 -1
  31. data/spec/fixtures/hook/.gitignore +2 -0
  32. data/spec/fixtures/hook/app/controllers/api/api_keys_controller.rb +1 -0
  33. data/spec/fixtures/hook/app/controllers/organizations_controller.rb +1 -0
  34. data/spec/fixtures/hook/app/models/api_key.rb +1 -0
  35. data/spec/fixtures/hook/app/models/configuration.rb +1 -0
  36. data/spec/fixtures/hook/app/models/show.rb +1 -0
  37. data/spec/fixtures/hook/app/models/user.rb +1 -0
  38. data/spec/fixtures/hook/kwargs.rb +11 -0
  39. data/spec/fixtures/hook/revoke_api_key.appmap.json +847 -0
  40. data/spec/fixtures/hook/spec/api_spec.rb +1 -0
  41. data/spec/fixtures/hook/spec/user_spec.rb +1 -0
  42. data/spec/fixtures/hook/user_page_scenario.appmap.json +1722 -0
  43. data/spec/fixtures/rails5_users_app/config/environments/test.rb +3 -0
  44. data/spec/fixtures/rails6_users_app/Dockerfile +0 -1
  45. data/spec/fixtures/rails6_users_app/appmap.yml +3 -1
  46. data/spec/fixtures/rails6_users_app/config/environments/test.rb +3 -0
  47. data/spec/fixtures/rails6_users_app/lib/tasks/appmap.rake +13 -0
  48. data/spec/hook_spec.rb +9 -0
  49. data/spec/rails_recording_spec.rb +1 -1
  50. data/spec/record_net_http_spec.rb +1 -0
  51. data/spec/swagger/swagger_spec.rb +47 -0
  52. data/test/{agent_setup_cli_test.rb → agent_setup_init_test.rb} +4 -4
  53. data/test/gem_test.rb +1 -0
  54. metadata +39 -19
  55. data/exe/appmap-agent-setup +0 -47
@@ -111,7 +111,7 @@ module AppMap
111
111
  end
112
112
 
113
113
  comment = method.comment
114
- function_info[:comment] = comment unless comment.blank?
114
+ function_info[:comment] = comment unless Util.blank?(comment)
115
115
 
116
116
  function_info[:labels] = parse_labels(comment) + (method.labels || [])
117
117
  object_infos << function_info
@@ -119,7 +119,7 @@ module AppMap
119
119
  parent = root
120
120
  object_infos.each do |info|
121
121
  parent = find_or_create parent.children, info do
122
- Types.const_get(info[:type].classify).new(info[:name].to_s).tap do |type|
122
+ Types.const_get(Util.classify(info[:type])).new(info[:name].to_s).tap do |type|
123
123
  info.keys.tap do |keys|
124
124
  keys.delete(:name)
125
125
  keys.delete(:type)
data/lib/appmap/config.rb CHANGED
@@ -5,6 +5,7 @@ require 'appmap/util'
5
5
  require 'appmap/handler/net_http'
6
6
  require 'appmap/handler/rails/template'
7
7
  require 'appmap/service/guesser'
8
+ require 'appmap/swagger/configuration'
8
9
 
9
10
  module AppMap
10
11
  class Config
@@ -81,8 +82,8 @@ module AppMap
81
82
  package_name: package_name,
82
83
  gem: gem,
83
84
  handler_class: handler_class.name,
84
- exclude: exclude.blank? ? nil : exclude,
85
- labels: labels.blank? ? nil : labels,
85
+ exclude: Util.blank?(exclude) ? nil : exclude,
86
+ labels: Util.blank?(labels) ? nil : labels,
86
87
  shallow: shallow
87
88
  }.compact
88
89
  end
@@ -226,15 +227,17 @@ module AppMap
226
227
  'JSON::Ext::Generator::State' => TargetMethods.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json.generate])),
227
228
  }.freeze
228
229
 
229
- attr_reader :name, :appmap_dir, :packages, :exclude, :hooked_methods, :builtin_hooks
230
+ attr_reader :name, :appmap_dir, :packages, :exclude, :swagger_config, :hooked_methods, :builtin_hooks
230
231
 
231
232
  def initialize(name,
232
233
  packages: [],
234
+ swagger_config: Swagger::Configuration.new,
233
235
  exclude: [],
234
236
  functions: [])
235
237
  @name = name
236
238
  @appmap_dir = AppMap::DEFAULT_APPMAP_DIR
237
239
  @packages = packages
240
+ @swagger_config = swagger_config
238
241
  @hook_paths = Set.new(packages.map(&:path))
239
242
  @exclude = exclude
240
243
  @builtin_hooks = BUILTIN_HOOKS
@@ -351,6 +354,11 @@ module AppMap
351
354
  end
352
355
  end
353
356
 
357
+ if config_data['swagger']
358
+ swagger_config = Swagger::Configuration.load(config_data['swagger'])
359
+ config_params[:swagger_config] = swagger_config
360
+ end
361
+
354
362
  Config.new name, config_params
355
363
  end
356
364
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'appmap/event'
4
+ require 'appmap/util'
4
5
 
5
6
  module AppMap
6
7
  module Handler
@@ -38,7 +39,7 @@ module AppMap
38
39
  headers: headers
39
40
  }.compact
40
41
 
41
- unless params.blank?
42
+ unless Util.blank?(params)
42
43
  h[:message] = params.keys.map do |key|
43
44
  val = params[key]
44
45
  {
@@ -81,7 +82,7 @@ module AppMap
81
82
  def request_headers(request)
82
83
  {}.tap do |headers|
83
84
  request.each_header do |k,v|
84
- key = [ 'HTTP', k.underscore.upcase ].join('_')
85
+ key = [ 'HTTP', Util.underscore(k).upcase ].join('_')
85
86
  headers[key] = v
86
87
  end
87
88
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'appmap/event'
4
4
  require 'appmap/hook'
5
+ require 'appmap/util'
5
6
 
6
7
  module AppMap
7
8
  module Handler
@@ -40,7 +41,7 @@ module AppMap
40
41
  headers: headers,
41
42
  }.compact
42
43
 
43
- unless params.blank?
44
+ unless Util.blank?(params)
44
45
  h[:message] = params.keys.map do |key|
45
46
  val = params[key]
46
47
  {
@@ -146,7 +146,8 @@ module AppMap
146
146
  class RenderHandler
147
147
  class << self
148
148
  def handle_call(defined_class, hook_method, receiver, args)
149
- context, options = args
149
+ # context, options
150
+ _, options = args
150
151
 
151
152
  warn "Renderer: #{options}" if LOG
152
153
 
data/lib/appmap/hook.rb CHANGED
@@ -99,7 +99,6 @@ module AppMap
99
99
  end
100
100
 
101
101
  def trace_end(trace_point)
102
- location = trace_location(trace_point)
103
102
  warn "Class or module ends at location #{trace_location(trace_point)}" if Hook::LOG || Hook::LOG_HOOK
104
103
 
105
104
  path = trace_point.path
@@ -18,6 +18,8 @@ module AppMap
18
18
  TIME_NOW = Time.method(:now)
19
19
  private_constant :TIME_NOW
20
20
 
21
+ ARRAY_OF_EMPTY_HASH = [{}.freeze].freeze
22
+
21
23
  def initialize(hook_package, hook_class, hook_method)
22
24
  @hook_package = hook_package
23
25
  @hook_class = hook_class
@@ -45,42 +47,48 @@ module AppMap
45
47
  after_hook = self.method(:after_hook)
46
48
  with_disabled_hook = self.method(:with_disabled_hook)
47
49
 
48
- hook_method_def = nil
49
- hook_class.instance_eval do
50
- hook_method_def = Proc.new do |*args, &block|
51
- instance_method = hook_method.bind(self).to_proc
52
- call_instance_method = -> { instance_method.call(*args, &block) }
50
+ hook_method_def = Proc.new do |*args, &block|
51
+ instance_method = hook_method.bind(self).to_proc
52
+ call_instance_method = -> {
53
+ # https://github.com/applandinc/appmap-ruby/issues/153
54
+ if Util.ruby_minor_version >= 2.7 && args == ARRAY_OF_EMPTY_HASH && hook_method.arity == 1
55
+ instance_method.call({}, &block)
56
+ else
57
+ instance_method.call(*args, &block)
58
+ end
59
+ }
53
60
 
54
- # We may not have gotten the class for the method during
55
- # initialization (e.g. for a singleton method on an embedded
56
- # struct), so make sure we have it now.
57
- defined_class, = Hook.qualify_method_name(hook_method) unless defined_class
61
+ # We may not have gotten the class for the method during
62
+ # initialization (e.g. for a singleton method on an embedded
63
+ # struct), so make sure we have it now.
64
+ defined_class, = Hook.qualify_method_name(hook_method) unless defined_class
58
65
 
59
- reentrant = Thread.current[HOOK_DISABLE_KEY]
60
- disabled_by_shallow_flag = \
61
- -> { hook_package&.shallow? && AppMap.tracing.last_package_for_current_thread == hook_package }
66
+ reentrant = Thread.current[HOOK_DISABLE_KEY]
67
+ disabled_by_shallow_flag = \
68
+ -> { hook_package&.shallow? && AppMap.tracing.last_package_for_current_thread == hook_package }
62
69
 
63
- enabled = true if AppMap.tracing.enabled? && !reentrant && !disabled_by_shallow_flag.call
70
+ enabled = true if AppMap.tracing.enabled? && !reentrant && !disabled_by_shallow_flag.call
64
71
 
65
- return call_instance_method.call unless enabled
72
+ return call_instance_method.call unless enabled
66
73
 
67
- call_event, start_time = with_disabled_hook.call do
68
- before_hook.call(self, defined_class, args)
69
- end
70
- return_value = nil
71
- exception = nil
72
- begin
73
- return_value = call_instance_method.call
74
- rescue
75
- exception = $ERROR_INFO
76
- raise
77
- ensure
78
- with_disabled_hook.call do
79
- after_hook.call(self, call_event, start_time, return_value, exception) if call_event
80
- end
74
+ call_event, start_time = with_disabled_hook.call do
75
+ before_hook.call(self, defined_class, args)
76
+ end
77
+ return_value = nil
78
+ exception = nil
79
+ begin
80
+ return_value = call_instance_method.call
81
+ rescue
82
+ exception = $ERROR_INFO
83
+ raise
84
+ ensure
85
+ with_disabled_hook.call do
86
+ after_hook.call(self, call_event, start_time, return_value, exception) if call_event
81
87
  end
82
88
  end
83
89
  end
90
+ hook_method_def = hook_method_def.ruby2_keywords if hook_method_def.respond_to?(:ruby2_keywords)
91
+
84
92
  hook_class.define_method_with_arity(hook_method.name, hook_method.arity, hook_method_def)
85
93
  end
86
94
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'appmap/util'
4
+
3
5
  module AppMap
4
6
  module Metadata
5
7
  class << self
@@ -40,9 +42,9 @@ module AppMap
40
42
  git_sha = `git rev-parse HEAD`.strip
41
43
  git_status = `git status -s`.split("\n").map(&:strip)
42
44
  git_last_annotated_tag = `git describe --abbrev=0 2>/dev/null`.strip
43
- git_last_annotated_tag = nil if git_last_annotated_tag.blank?
45
+ git_last_annotated_tag = nil if Util.blank?(git_last_annotated_tag)
44
46
  git_last_tag = `git describe --abbrev=0 --tags 2>/dev/null`.strip
45
- git_last_tag = nil if git_last_tag.blank?
47
+ git_last_tag = nil if Util.blank?(git_last_tag)
46
48
  git_commits_since_last_annotated_tag = `git describe`.strip =~ /-(\d+)-(\w+)$/[1] rescue 0 if git_last_annotated_tag
47
49
  git_commits_since_last_tag = `git describe --tags`.strip =~ /-(\d+)-(\w+)$/[1] rescue 0 if git_last_tag
48
50
 
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'appmap/util'
4
+ require 'active_support'
5
+ require 'active_support/core_ext'
4
6
 
5
7
  module AppMap
6
8
  # Integration of AppMap with Minitest. When enabled with APPMAP=true, the AppMap tracer will
@@ -5,8 +5,8 @@ module AppMap
5
5
  class Railtie < ::Rails::Railtie
6
6
  initializer 'appmap.remote_recording' do
7
7
  require 'appmap/middleware/remote_recording'
8
- Rails.application.config.middleware.insert_after \
9
- Rails::Rack::Logger,
8
+ Rails.application.config.middleware.insert_before \
9
+ ActionDispatch::Executor,
10
10
  AppMap::Middleware::RemoteRecording
11
11
  end
12
12
 
data/lib/appmap/rspec.rb CHANGED
@@ -62,8 +62,10 @@ module AppMap
62
62
  example_group_parent = \
63
63
  if example_group.respond_to?(:module_parent)
64
64
  example_group.module_parent
65
- else
65
+ elsif example_group.respond_to?(:parent)
66
66
  example_group.parent
67
+ elsif example_group.respond_to?(:parent_groups)
68
+ example_group.parent_groups.first
67
69
  end
68
70
 
69
71
  example_group_parent != example_group ? ScopeExampleGroup.new(example_group_parent) : nil
@@ -109,13 +111,13 @@ module AppMap
109
111
 
110
112
  description = []
111
113
  scope = ScopeExample.new(example)
112
-
113
114
  while scope
114
115
  description << scope.description
115
116
  scope = scope.parent
116
117
  end
117
118
 
118
- description.reject!(&:nil?).reject!(&:blank?)
119
+ description.reject!(&:nil?)
120
+ description.reject!(&Util.method(:blank?))
119
121
  default_description = description.last
120
122
  description.reverse!
121
123
 
@@ -0,0 +1,2 @@
1
+ require 'appmap/swagger/configuration'
2
+ require 'appmap/swagger/rake_tasks'
@@ -0,0 +1,70 @@
1
+ require 'yaml'
2
+
3
+ module AppMap
4
+ module Swagger
5
+ class Configuration
6
+ DEFAULT_VERSION = '1.0'
7
+ DEFAULT_OUTPUT_DIR = 'swagger'
8
+ DEFAULT_DESCRIPTION = 'Generate Swagger from AppMaps'
9
+
10
+ attr_accessor :project_version,
11
+ :output_dir,
12
+ :description
13
+ attr_writer :project_name, :template
14
+
15
+ class << self
16
+ def load(config_data)
17
+ Configuration.new.tap do |config|
18
+ config_data.each do |k,v|
19
+ config.send "#{k}=", v
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ def initialize
26
+ @project_name = nil
27
+ @project_version = DEFAULT_VERSION
28
+ @output_dir = DEFAULT_OUTPUT_DIR
29
+ @description = DEFAULT_DESCRIPTION
30
+ end
31
+
32
+ def project_name
33
+ @project_name || default_project_name
34
+ end
35
+
36
+ def template
37
+ @template || default_template
38
+ end
39
+
40
+ def default_template
41
+ YAML.load <<~TEMPLATE
42
+ openapi: 3.0.1
43
+ info:
44
+ title: #{project_name}
45
+ version: #{project_version}
46
+ paths:
47
+ components:
48
+ servers:
49
+ - url: http://{defaultHost}
50
+ variables:
51
+ defaultHost:
52
+ default: localhost:3000
53
+ TEMPLATE
54
+ end
55
+
56
+ def default_project_name
57
+ # https://www.rubydoc.info/docs/rails/Module#module_parent_name-instance_method
58
+ module_parent_name = ->(cls) { cls.name =~ /::[^:]+\Z/ ? $`.freeze : nil }
59
+
60
+ # Lazy-evaluate this so that Rails.application will be defined.
61
+ # If this code runs too early in the lifecycle, Rails.application is nil.
62
+ if defined?(::Rails)
63
+ [module_parent_name.(::Rails.application.class).humanize.titleize, "API"].join(" ")
64
+ else
65
+ "MyProject API"
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,43 @@
1
+ require 'rdoc'
2
+ require 'reverse_markdown'
3
+
4
+ module AppMap
5
+ module Swagger
6
+ # Transform description fields into Markdown.
7
+ class MarkdownDescriptions
8
+ def initialize(swagger_yaml)
9
+ @swagger_yaml = swagger_yaml
10
+ end
11
+
12
+ def converter
13
+ method(:rdoc_to_markdown)
14
+ end
15
+
16
+ def perform
17
+ to_markdown = lambda do |obj|
18
+ return obj.each(&to_markdown) if obj.is_a?(Array)
19
+ return unless obj.is_a?(Hash)
20
+
21
+ description = obj['description']
22
+ obj['description'] = converter.(description) if description
23
+
24
+ obj.reject { |k,v| k == 'properties' }.each_value(&to_markdown)
25
+
26
+ obj
27
+ end
28
+
29
+ to_markdown.(Util.deep_dup(@swagger_yaml))
30
+ end
31
+
32
+ protected
33
+
34
+ def rdoc_to_markdown(comment)
35
+ # Strip tags
36
+ comment = comment.split("\n").reject { |line| line =~ /^\s*@/ }.join("\n")
37
+ converter = ::RDoc::Markup::ToHtml.new(::RDoc::Options.new)
38
+ html = converter.convert(comment).strip
39
+ ::ReverseMarkdown.convert(html).strip
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,43 @@
1
+ require 'rake'
2
+ require 'yaml'
3
+ require 'appmap/node_cli'
4
+ require 'appmap/swagger/markdown_descriptions'
5
+ require 'appmap/swagger/stable'
6
+
7
+ module AppMap
8
+ module Swagger
9
+ module RakeTasks
10
+ extend self
11
+ extend Rake::DSL
12
+
13
+ def configuration
14
+ AppMap.configuration
15
+ end
16
+
17
+ def define_tasks
18
+ generate_swagger = lambda do |t, args|
19
+ appmap_js = AppMap::NodeCLI.new(verbose: Rake.verbose == true)
20
+
21
+ FileUtils.mkdir_p configuration.swagger_config.output_dir
22
+
23
+ cmd = %w[swagger]
24
+
25
+ swagger_raw, = appmap_js.command(cmd)
26
+
27
+ gen_swagger = YAML.load(swagger_raw)
28
+ gen_swagger_full = AppMap::Swagger::MarkdownDescriptions.new(gen_swagger).perform
29
+ gen_swagger_stable = AppMap::Swagger::Stable.new(gen_swagger).perform
30
+
31
+ swagger = configuration.swagger_config.template.merge(gen_swagger_full)
32
+ File.write File.join(configuration.swagger_config.output_dir, 'openapi.yaml'), YAML.dump(swagger)
33
+
34
+ swagger = configuration.swagger_config.template.merge(gen_swagger_stable)
35
+ File.write File.join(configuration.swagger_config.output_dir, 'openapi_stable.yaml'), YAML.dump(swagger)
36
+ end
37
+
38
+ desc configuration.swagger_config.description
39
+ task :swagger, &generate_swagger
40
+ end
41
+ end
42
+ end
43
+ end