appmap 0.40.0 → 0.42.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +49 -7
  4. data/lib/appmap.rb +5 -0
  5. data/lib/appmap/command/record.rb +1 -1
  6. data/lib/appmap/config.rb +48 -28
  7. data/lib/appmap/event.rb +14 -4
  8. data/lib/appmap/hook.rb +24 -2
  9. data/lib/appmap/hook/method.rb +1 -1
  10. data/lib/appmap/middleware/remote_recording.rb +1 -1
  11. data/lib/appmap/minitest.rb +17 -14
  12. data/lib/appmap/rspec.rb +13 -78
  13. data/lib/appmap/version.rb +1 -1
  14. data/spec/abstract_controller_base_spec.rb +2 -2
  15. data/spec/config_spec.rb +1 -0
  16. data/spec/fixtures/hook/exclude.rb +15 -0
  17. data/spec/fixtures/rails5_users_app/appmap.yml +4 -1
  18. data/spec/fixtures/rails5_users_app/docker-compose.yml +3 -0
  19. data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_api_spec.rb +3 -3
  20. data/spec/fixtures/rails5_users_app/spec/models/user_spec.rb +2 -12
  21. data/spec/fixtures/rails6_users_app/appmap.yml +4 -1
  22. data/spec/fixtures/rails6_users_app/docker-compose.yml +3 -0
  23. data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_api_spec.rb +3 -3
  24. data/spec/fixtures/rails6_users_app/spec/models/user_spec.rb +2 -12
  25. data/spec/hook_spec.rb +11 -1
  26. data/spec/record_sql_rails_pg_spec.rb +1 -1
  27. data/test/fixtures/gem_test/appmap.yml +1 -1
  28. data/test/fixtures/gem_test/test/parser_test.rb +12 -0
  29. data/test/fixtures/rspec_recorder/spec/decorated_hello_spec.rb +3 -3
  30. data/test/fixtures/rspec_recorder/spec/plain_hello_spec.rb +1 -1
  31. data/test/gem_test.rb +4 -4
  32. data/test/minitest_test.rb +1 -2
  33. data/test/rspec_test.rb +1 -7
  34. metadata +4 -4
  35. data/spec/rspec_feature_metadata_spec.rb +0 -31
  36. data/test/fixtures/gem_test/test/to_param_test.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b0943dadde84e67780d724918fa26377b869791eaab6edfb714e5081ecb5c80
4
- data.tar.gz: c492323d80541a913b800924d04a7c24a6c8682263ba3ec4deabfa8d7ade3908
3
+ metadata.gz: c70b96dcf283fa03ea3acc787a78ecf3a732a78808834ba0acf09d4e398172c6
4
+ data.tar.gz: ec83f4e0eb8908e971bc67e27696e5b37252289a50b848b85bd54e56520d3a75
5
5
  SHA512:
6
- metadata.gz: 2a27e5b9bc3dafbba8aafd7035732b6ffcb813d57bb216d0e544af30260c0e26f23a920d1b2d6d35882046e74a57f8b492b12b9869de5d820ff6a4e09cafd716
7
- data.tar.gz: f1bdc22fda7a6fdf0a37fc1d5bd419947e0fffa16511873dbe8a1549f30bf862566fe74fa4c9952c4338f5263414c99f3d598daec0a599b467e52ebac374b4f7
6
+ metadata.gz: 1e19e607c94eee4cfebf6646f1802e27446330807a1dd252bbd94d823def31ed9abf59668f3d0672a6bfac9261e076338fe13d27a0dbea81e83da80ff726cf7c
7
+ data.tar.gz: 3221cdfb7c164efecbe022f89829c4e80568b3fa5a7343c2850c8e1d4387f807e5ebcba0d04345bd5641ab3ee9a32d92faba3337dfe5f4bbe5f254fd9320d4a4
data/CHANGELOG.md CHANGED
@@ -1,3 +1,29 @@
1
+ # v0.42.1
2
+
3
+ * Add missing require `set`.
4
+ * Check `cls.respond_to?(:singleton_class)`, since it oddly, may not.
5
+
6
+ # v0.42.0
7
+
8
+ * Remove `feature_group` and `feature` metadata from minitest and RSpec AppMaps.
9
+ * Add `metadata.source_location`.
10
+
11
+ # v0.41.2
12
+
13
+ * Don't rely on `gemspec.source_paths` to list all the source locations in a gem. Hook any code that's loaded
14
+ from inside the `gem_dir`.
15
+
16
+ # v0.41.1
17
+
18
+ * Make best effort to ensure that class name is not `null` in the appmap.json.
19
+ * Don't try and instrument gems which are a dependency of the this gem.
20
+ * Fix a nil exception when applying the exclude list to builtins.
21
+
22
+ # v0.41.0
23
+
24
+ * Adjust some label names to match `provider.*`, `format.*`.
25
+ * Add global `exclude` list to *appmap.yml* which can be used to definitively exclude specific classes and methods.
26
+
1
27
  # v0.40.0
2
28
 
3
29
  * Parse source code comments into function labels.
data/README.md CHANGED
@@ -9,6 +9,7 @@
9
9
  - [Minitest](#minitest)
10
10
  - [Cucumber](#cucumber)
11
11
  - [Remote recording](#remote-recording)
12
+ - [Server process recording](#server-process-recording)
12
13
  - [AppMap for VSCode](#appmap-for-vscode)
13
14
  - [Uploading AppMaps](#uploading-appmaps)
14
15
  - [Development](#development)
@@ -53,15 +54,23 @@ Support for new versions is added frequently, please check back regularly for up
53
54
  <a href="https://www.loom.com/share/78ab32a312ff4b85aa8827a37f1cb655"> <p>Quick and easy setup of the AppMap gem for Rails - Watch Video</p> <img style="max-width:300px;" src="https://cdn.loom.com/sessions/thumbnails/78ab32a312ff4b85aa8827a37f1cb655-with-play.gif"> </a>
54
55
 
55
56
 
56
- Add `gem 'appmap'` to your Gemfile just as you would any other dependency. We recommend that the Gem be added to the `:development, :test` section.
57
+ Add `gem 'appmap'` to **beginning** of your Gemfile. We recommend that you add the `appmap` gem to the `:development, :test` group. Your Gemfile should look something like this:
57
58
 
58
59
  ```
60
+ source 'https://rubygems.org'
61
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
62
+
63
+ # Optional rubRuby version
64
+ # ruby '2.7.2'
65
+
59
66
  group :development, :test do
60
67
  gem 'appmap'
61
68
  end
62
69
  ```
63
70
 
64
- Then install with `bundle`.
71
+ Install with `bundle install`, as usual.
72
+
73
+ It's important to add the `appmap` gem before any other gems that you may want to instrument. There is more about this in the section on adding gems to the *appmap.yml*.
65
74
 
66
75
  **Railtie**
67
76
 
@@ -73,13 +82,30 @@ If you are using Ruby on Rails, require the railtie after Rails is loaded.
73
82
  require 'appmap/railtie' if defined?(AppMap).
74
83
  ```
75
84
 
85
+ **application.rb**
86
+
87
+ Add this line to *application.rb*, to enable server recording with `APPMAP_RECORD=true`:
88
+
89
+ ```ruby
90
+ module MyApp
91
+ class Application < Rails::Application
92
+ ...
93
+
94
+ config.appmap.enabled = true if ENV['APPMAP_RECORD']
95
+
96
+ ...
97
+ end
98
+ end
99
+ ```
100
+
76
101
  # Configuration
77
102
 
78
103
  When you run your program, the `appmap` gem reads configuration settings from `appmap.yml`. Here's a sample configuration
79
104
  file for a typical Rails project:
80
105
 
81
106
  ```yaml
82
- name: MyProject
107
+ # 'name' should generally be the same as the code repo name.
108
+ name: my_project
83
109
  packages:
84
110
  - path: app/controllers
85
111
  - path: app/models
@@ -91,10 +117,15 @@ packages:
91
117
  - gem: devise
92
118
  - gem: aws-sdk
93
119
  - gem: will_paginate
120
+ exclude:
121
+ - MyClass
122
+ - MyClass#my_instance_method
123
+ - MyClass.my_class_method
94
124
  ```
95
125
 
96
126
  * **name** Provides the project name (required)
97
- * **packages** A list of source code directories which should be instrumented.
127
+ * **packages** A list of source code directories which should be recorded.
128
+ * **exclude** A list of classes and/or methods to definitively exclude from recording.
98
129
 
99
130
  **packages**
100
131
 
@@ -102,13 +133,18 @@ Each entry in the `packages` list is a YAML object which has the following keys:
102
133
 
103
134
  * **path** The path to the source code directory. The path may be relative to the current working directory, or it may
104
135
  be an absolute path.
105
- * **gem** As an alternative to specifying the path, specify the name of a dependency gem. When using `gem`, don't specify `path`.
136
+ * **gem** As an alternative to specifying the path, specify the name of a dependency gem. When using `gem`, don't specify `path`. In your `Gemfile`, the `appmap` gem **must** be listed **before** any gem that you specify in your *appmap.yml*.
106
137
  * **exclude** A list of files and directories which will be ignored. By default, all modules, classes and public
107
- functions are inspected.
138
+ functions are inspected. See also: global `exclude` list.
108
139
  * **shallow** When set to `true`, only the first function call entry into a package will be recorded. Subsequent function calls within
109
140
  the same package are not recorded unless code execution leaves the package and re-enters it. Default: `true` when using `gem`,
110
141
  `false` when using `path`.
111
142
 
143
+ **exclude**
144
+
145
+ Optional list of fully qualified class and method names. Separate class and method names with period (`.`) for class methods and hash (`#`) for instance methods.
146
+
147
+
112
148
  # Labels
113
149
 
114
150
  The [AppMap data format](https://github.com/applandinc/appmap) provides for class and function `labels`, which can be used to enhance the AppMap visualizations, and to programatically analyze the data.
@@ -287,6 +323,12 @@ $ bundle exec rails server
287
323
 
288
324
  6. Open the AppLand browser extension and push `Stop`. The recording will be transferred to the AppLand website and opened in your browser.
289
325
 
326
+ ## Server process recording
327
+
328
+ Run your Rails server with `APPMAP_RECORD=true`. When the server exits, an *appmap.json* file will be written to the project directory. This is a great way to start the server, interact with your app as a user (or through it's API), and then view an AppMap of everything that happened.
329
+
330
+ Be sure and set `WEB_CONCURRENCY=1`, if you are using a webserver that can run multiple processes. You only want there to be one process while you are recording, otherwise they will both try and write *appmap.json* and one of them will clobber the other.
331
+
290
332
  # AppMap for VSCode
291
333
 
292
334
  The [AppMap extension for VSCode](https://marketplace.visualstudio.com/items?itemName=appland.appmap) is a great way to onboard developers to new code, and troubleshoot hard-to-understand bugs with visuals.
@@ -298,7 +340,7 @@ The [AppMap extension for VSCode](https://marketplace.visualstudio.com/items?ite
298
340
  For instructions on uploading, see the documentation of the [AppLand CLI](https://github.com/applandinc/appland-cli).
299
341
 
300
342
  # Development
301
- [![Build Status](https://travis-ci.org/applandinc/appmap-ruby.svg?branch=master)](https://travis-ci.org/applandinc/appmap-ruby)
343
+ [![Build Status](https://travis-ci.com/applandinc/appmap-ruby.svg?branch=master)](https://travis-ci.com/applandinc/appmap-ruby)
302
344
 
303
345
  ## Running tests
304
346
 
data/lib/appmap.rb CHANGED
@@ -50,6 +50,11 @@ module AppMap
50
50
  end
51
51
  end
52
52
 
53
+ # Whether to include source and comments in all class maps.
54
+ def include_source?
55
+ ENV['APPMAP_SOURCE'] == 'true'
56
+ end
57
+
53
58
  # Used to start tracing, stop tracing, and record events.
54
59
  def tracing
55
60
  @tracing ||= Trace::Tracing.new
@@ -27,7 +27,7 @@ module AppMap
27
27
  event_thread.join
28
28
  yield AppMap::APPMAP_FORMAT_VERSION,
29
29
  AppMap.detect_metadata,
30
- AppMap.class_map(tracer.event_methods),
30
+ AppMap.class_map(tracer.event_methods, include_source: AppMap.include_source?),
31
31
  events
32
32
  end
33
33
 
data/lib/appmap/config.rb CHANGED
@@ -16,20 +16,20 @@ module AppMap
16
16
  end
17
17
 
18
18
  def build_from_gem(gem, shallow: true, package_name: nil, exclude: [], labels: [])
19
- gem_paths(gem).map do |gem_path|
20
- Package.new(gem_path, gem, package_name, exclude, labels, shallow)
19
+ if %w[method_source activesupport].member?(gem)
20
+ warn "WARNING: #{gem} cannot be AppMapped because it is a dependency of the appmap gem"
21
+ return
21
22
  end
23
+ Package.new(gem_path(gem), gem, package_name, exclude, labels, shallow)
22
24
  end
23
25
 
24
26
  private_class_method :new
25
27
 
26
28
  protected
27
29
 
28
- def gem_paths(gem)
30
+ def gem_path(gem)
29
31
  gemspec = Gem.loaded_specs[gem] or raise "Gem #{gem.inspect} not found"
30
- gemspec.source_paths.map do |path|
31
- File.join(gemspec.gem_dir, path)
32
- end
32
+ gemspec.gem_dir
33
33
  end
34
34
  end
35
35
 
@@ -57,32 +57,36 @@ module AppMap
57
57
  # Methods that should always be hooked, with their containing
58
58
  # package and labels that should be applied to them.
59
59
  HOOKED_METHODS = {
60
- 'ActiveSupport::SecurityUtils' => Hook.new(:secure_compare, Package.build_from_path('active_support', package_name: 'active_support', labels: %w[security crypto])),
61
- 'ActionView::Renderer' => Hook.new(:render, Package.build_from_path('action_view', package_name: 'action_view', labels: %w[view]))
60
+ 'ActiveSupport::SecurityUtils' => Hook.new(:secure_compare, Package.build_from_path('active_support', labels: %w[provider.secure_compare])),
61
+ 'ActionView::Renderer' => Hook.new(:render, Package.build_from_path('action_view', labels: %w[mvc.view])),
62
+ 'ActionDispatch::Cookies::CookieJar' => Hook.new(%i[[]= clear update delete recycle], Package.build_from_path('action_pack', labels: %w[provider.http.cookie])),
63
+ 'ActionDispatch::Cookies::EncryptedCookieJar' => Hook.new(%i[[]=], Package.build_from_path('action_pack', labels: %w[provider.http.cookie crypto])),
64
+ 'CanCan::ControllerAdditions' => Hook.new(%i[authorize! can? cannot?], Package.build_from_path('cancancan', labels: %w[provider.authorization])),
65
+ 'CanCan::Ability' => Hook.new(%i[authorize!], Package.build_from_path('cancancan', labels: %w[provider.authorization])),
62
66
  }.freeze
63
67
 
64
68
  BUILTIN_METHODS = {
65
69
  'OpenSSL::PKey::PKey' => Hook.new(:sign, OPENSSL_PACKAGES),
66
- 'Digest::Instance' => Hook.new(:digest, OPENSSL_PACKAGES),
67
70
  'OpenSSL::X509::Request' => Hook.new(%i[sign verify], OPENSSL_PACKAGES),
68
71
  'OpenSSL::PKCS5' => Hook.new(%i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGES),
69
72
  'OpenSSL::Cipher' => Hook.new(%i[encrypt decrypt final], OPENSSL_PACKAGES),
70
73
  'OpenSSL::X509::Certificate' => Hook.new(:sign, OPENSSL_PACKAGES),
71
- 'Net::HTTP' => Hook.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[http io])),
72
- 'Net::SMTP' => Hook.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[smtp email io])),
73
- 'Net::POP3' => Hook.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[pop pop3 email io])),
74
- 'Net::IMAP' => Hook.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[imap email io])),
75
- 'Marshal' => Hook.new(%i[dump load], Package.build_from_path('marshal', labels: %w[serialization marshal])),
76
- 'Psych' => Hook.new(%i[dump dump_stream load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[serialization yaml])),
77
- 'JSON::Ext::Parser' => Hook.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[serialization json])),
78
- 'JSON::Ext::Generator::State' => Hook.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[serialization json])),
74
+ 'Net::HTTP' => Hook.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[protocol.http io])),
75
+ 'Net::SMTP' => Hook.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[protocol.smtp protocol.email io])),
76
+ 'Net::POP3' => Hook.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[protocol.pop protocol.email io])),
77
+ 'Net::IMAP' => Hook.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[protocol.imap protocol.email io])),
78
+ 'Marshal' => Hook.new(%i[dump load], Package.build_from_path('marshal', labels: %w[format.marshal provider.serialization])),
79
+ 'Psych' => Hook.new(%i[dump dump_stream load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml provider.serialization])),
80
+ 'JSON::Ext::Parser' => Hook.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[format.json provider.serialization])),
81
+ 'JSON::Ext::Generator::State' => Hook.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json provider.serialization])),
79
82
  }.freeze
80
83
 
81
- attr_reader :name, :packages
84
+ attr_reader :name, :packages, :exclude
82
85
 
83
- def initialize(name, packages = [])
86
+ def initialize(name, packages = [], exclude = [])
84
87
  @name = name
85
88
  @packages = packages
89
+ @exclude = exclude
86
90
  end
87
91
 
88
92
  class << self
@@ -105,44 +109,60 @@ module AppMap
105
109
  shallow = true if shallow.nil?
106
110
  Package.build_from_gem(gem, exclude: package['exclude'] || [], shallow: shallow)
107
111
  else
108
- [ Package.build_from_path(path, exclude: package['exclude'] || [], shallow: package['shallow']) ]
112
+ Package.build_from_path(path, exclude: package['exclude'] || [], shallow: package['shallow'])
109
113
  end
110
- end.flatten
111
- Config.new config_data['name'], packages
114
+ end.compact
115
+ Config.new config_data['name'], packages, config_data['exclude'] || []
112
116
  end
113
117
  end
114
118
 
115
119
  def to_h
116
120
  {
117
121
  name: name,
118
- packages: packages.map(&:to_h)
122
+ packages: packages.map(&:to_h),
123
+ exclude: exclude
119
124
  }
120
125
  end
121
126
 
127
+ # package_for_method finds the Package, if any, which configures the hook
128
+ # for a method.
122
129
  def package_for_method(method)
130
+ package_hooked_by_class(method) || package_hooked_by_source_location(method)
131
+ end
132
+
133
+ def package_hooked_by_class(method)
123
134
  defined_class, _, method_name = ::AppMap::Hook.qualify_method_name(method)
124
- package = find_package(defined_class, method_name)
125
- return package if package
135
+ return find_package(defined_class, method_name)
136
+ end
126
137
 
138
+ def package_hooked_by_source_location(method)
127
139
  location = method.source_location
128
140
  location_file, = location
129
141
  return unless location_file
130
142
 
131
143
  location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0
132
- packages.find do |pkg|
144
+ packages.select { |pkg| pkg.path }.find do |pkg|
133
145
  (location_file.index(pkg.path) == 0) &&
134
146
  !pkg.exclude.find { |p| location_file.index(p) }
135
147
  end
136
148
  end
137
149
 
138
- def included_by_location?(method)
139
- !!package_for_method(method)
150
+ def never_hook?(method)
151
+ defined_class, separator, method_name = ::AppMap::Hook.qualify_method_name(method)
152
+ return true if exclude.member?(defined_class) || exclude.member?([ defined_class, separator, method_name ].join)
140
153
  end
141
154
 
155
+ # always_hook? indicates a method that should always be hooked.
142
156
  def always_hook?(defined_class, method_name)
143
157
  !!find_package(defined_class, method_name)
144
158
  end
145
159
 
160
+ # included_by_location? indicates a method whose source location matches a method definition that has been
161
+ # configured for inclusion.
162
+ def included_by_location?(method)
163
+ !!package_for_method(method)
164
+ end
165
+
146
166
  def find_package(defined_class, method_name)
147
167
  hook = find_hook(defined_class)
148
168
  return nil unless hook
data/lib/appmap/event.rb CHANGED
@@ -38,6 +38,15 @@ module AppMap
38
38
 
39
39
  protected
40
40
 
41
+ # Heuristic for dynamically defined class whose name can be nil
42
+ def best_class_name(value)
43
+ value_cls = value.class
44
+ while value_cls.name.nil?
45
+ value_cls = value_cls.superclass
46
+ end
47
+ value_cls.name
48
+ end
49
+
41
50
  def custom_display_string(value)
42
51
  case value
43
52
  when File
@@ -77,6 +86,7 @@ module AppMap
77
86
 
78
87
  class << self
79
88
  def build_from_invocation(mc = MethodCall.new, defined_class, method, receiver, arguments)
89
+ defined_class ||= 'Class'
80
90
  mc.tap do
81
91
  static = receiver.is_a?(Module)
82
92
  mc.defined_class = defined_class
@@ -110,14 +120,14 @@ module AppMap
110
120
  end
111
121
  {
112
122
  name: param_name,
113
- class: value.class.name,
123
+ class: best_class_name(value),
114
124
  object_id: value.__id__,
115
125
  value: display_string(value),
116
126
  kind: param_type
117
127
  }
118
128
  end
119
129
  mc.receiver = {
120
- class: receiver.class.name,
130
+ class: best_class_name(receiver),
121
131
  object_id: receiver.__id__,
122
132
  value: display_string(receiver)
123
133
  }
@@ -172,7 +182,7 @@ module AppMap
172
182
  mr.tap do |_|
173
183
  if return_value
174
184
  mr.return_value = {
175
- class: return_value.class.name,
185
+ class: best_class_name(return_value),
176
186
  value: display_string(return_value),
177
187
  object_id: return_value.__id__
178
188
  }
@@ -183,7 +193,7 @@ module AppMap
183
193
  while next_exception
184
194
  exception_backtrace = next_exception.backtrace_locations.try(:[], 0)
185
195
  exceptions << {
186
- class: next_exception.class.name,
196
+ class: best_class_name(next_exception),
187
197
  message: next_exception.message,
188
198
  object_id: next_exception.__id__,
189
199
  path: exception_backtrace&.path,
data/lib/appmap/hook.rb CHANGED
@@ -46,7 +46,16 @@ module AppMap
46
46
  cls = trace_point.self
47
47
 
48
48
  instance_methods = cls.public_instance_methods(false) - OBJECT_INSTANCE_METHODS
49
- class_methods = cls.singleton_class.public_instance_methods(false) - instance_methods - OBJECT_STATIC_METHODS
49
+ # NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
50
+ class_methods = begin
51
+ if cls.respond_to?(:singleton_class)
52
+ cls.singleton_class.public_instance_methods(false) - instance_methods - OBJECT_STATIC_METHODS
53
+ else
54
+ []
55
+ end
56
+ rescue NameError
57
+ []
58
+ end
50
59
 
51
60
  hook = lambda do |hook_cls|
52
61
  lambda do |method_id|
@@ -63,6 +72,8 @@ module AppMap
63
72
  # Skip methods that have no instruction sequence, as they are obviously trivial.
64
73
  next unless disasm
65
74
 
75
+ next if config.never_hook?(method)
76
+
66
77
  next unless \
67
78
  config.always_hook?(hook_cls, method.name) ||
68
79
  config.included_by_location?(method)
@@ -78,12 +89,20 @@ module AppMap
78
89
  end
79
90
 
80
91
  instance_methods.each(&hook.(cls))
81
- class_methods.each(&hook.(cls.singleton_class))
92
+ # NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
93
+ begin
94
+ class_methods.each(&hook.(cls.singleton_class)) if cls.respond_to?(:singleton_class)
95
+ rescue NameError
96
+ # NameError:
97
+ # uninitialized constant Faraday::Connection
98
+ end
82
99
  end
83
100
 
84
101
  tp.enable(&block)
85
102
  end
86
103
 
104
+ # hook_builtins builds hooks for code that is built in to the Ruby standard library.
105
+ # No TracePoint events are emitted for builtins, so a separate hooking mechanism is needed.
87
106
  def hook_builtins
88
107
  return unless self.class.lock_builtins
89
108
 
@@ -97,6 +116,7 @@ module AppMap
97
116
  require hook.package.package_name if hook.package.package_name
98
117
  Array(hook.method_names).each do |method_name|
99
118
  method_name = method_name.to_sym
119
+
100
120
  cls = class_from_string.(class_name)
101
121
  method = \
102
122
  begin
@@ -105,6 +125,8 @@ module AppMap
105
125
  cls.method(method_name) rescue nil
106
126
  end
107
127
 
128
+ next if config.never_hook?(method)
129
+
108
130
  if method
109
131
  Hook::Method.new(hook.package, cls, method).activate
110
132
  else
@@ -35,7 +35,7 @@ module AppMap
35
35
  else
36
36
  "#{hook_method.name} (class resolution deferred)"
37
37
  end
38
- warn "AppMap: Hooking " + msg
38
+ warn "AppMap: Hooking #{msg} at line #{(hook_method.source_location || []).join(':')}"
39
39
  end
40
40
 
41
41
  defined_class = @defined_class
@@ -67,7 +67,7 @@ module AppMap
67
67
 
68
68
  response = JSON.generate \
69
69
  version: AppMap::APPMAP_FORMAT_VERSION,
70
- classMap: AppMap.class_map(tracer.event_methods),
70
+ classMap: AppMap.class_map(tracer.event_methods, include_source: AppMap.include_source?),
71
71
  metadata: metadata,
72
72
  events: @events
73
73
 
@@ -7,20 +7,25 @@ module AppMap
7
7
  # be activated around each test.
8
8
  module Minitest
9
9
  APPMAP_OUTPUT_DIR = 'tmp/appmap/minitest'
10
- LOG = false
10
+ LOG = ( ENV['APPMAP_DEBUG'] == 'true' || ENV['DEBUG'] == 'true' )
11
11
 
12
12
  def self.metadata
13
13
  AppMap.detect_metadata
14
14
  end
15
15
 
16
- Recording = Struct.new(:test) do
17
- def initialize(test)
16
+ Recording = Struct.new(:test, :test_name) do
17
+ def initialize(test, test_name)
18
18
  super
19
19
 
20
- warn "Starting recording of test #{test.class}.#{test.name}" if AppMap::Minitest::LOG
20
+ warn "Starting recording of test #{test.class}.#{test.name}@#{source_location}" if AppMap::Minitest::LOG
21
21
  @trace = AppMap.tracing.trace
22
22
  end
23
23
 
24
+ def source_location
25
+ test.method(test_name).source_location.join(':')
26
+ end
27
+
28
+
24
29
  def finish
25
30
  warn "Finishing recording of test #{test.class}.#{test.name}" if AppMap::Minitest::LOG
26
31
 
@@ -31,7 +36,7 @@ module AppMap
31
36
 
32
37
  AppMap::Minitest.add_event_methods @trace.event_methods
33
38
 
34
- class_map = AppMap.class_map(@trace.event_methods)
39
+ class_map = AppMap.class_map(@trace.event_methods, include_source: AppMap.include_source?)
35
40
 
36
41
  feature_group = test.class.name.underscore.split('_')[0...-1].join('_').capitalize
37
42
  feature_name = test.name.split('_')[1..-1].join(' ')
@@ -39,9 +44,8 @@ module AppMap
39
44
 
40
45
  AppMap::Minitest.save scenario_name,
41
46
  class_map,
42
- events: events,
43
- feature_name: feature_name,
44
- feature_group_name: feature_group
47
+ source_location,
48
+ events: events
45
49
  end
46
50
  end
47
51
 
@@ -55,8 +59,8 @@ module AppMap
55
59
  FileUtils.mkdir_p APPMAP_OUTPUT_DIR
56
60
  end
57
61
 
58
- def begin_test(test)
59
- @recordings_by_test[test.object_id] = Recording.new(test)
62
+ def begin_test(test, name)
63
+ @recordings_by_test[test.object_id] = Recording.new(test, name)
60
64
  end
61
65
 
62
66
  def end_test(test)
@@ -74,12 +78,11 @@ module AppMap
74
78
  @event_methods += event_methods
75
79
  end
76
80
 
77
- def save(example_name, class_map, events: nil, feature_name: nil, feature_group_name: nil, labels: nil)
81
+ def save(example_name, class_map, source_location, events: nil, labels: nil)
78
82
  metadata = AppMap::Minitest.metadata.tap do |m|
79
83
  m[:name] = example_name
84
+ m[:source_location] = source_location
80
85
  m[:app] = AppMap.configuration.name
81
- m[:feature] = feature_name if feature_name
82
- m[:feature_group] = feature_group_name if feature_group_name
83
86
  m[:frameworks] ||= []
84
87
  m[:frameworks] << {
85
88
  name: 'minitest',
@@ -128,7 +131,7 @@ if AppMap::Minitest.enabled?
128
131
  alias run_without_hook run
129
132
 
130
133
  def run
131
- AppMap::Minitest.begin_test self
134
+ AppMap::Minitest.begin_test self, name
132
135
  begin
133
136
  run_without_hook
134
137
  ensure
data/lib/appmap/rspec.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'appmap/util'
4
+ require 'set'
4
5
 
5
6
  module AppMap
6
7
  # Integration of AppMap with RSpec. When enabled with APPMAP=true, the AppMap tracer will
@@ -13,58 +14,9 @@ module AppMap
13
14
  AppMap.detect_metadata
14
15
  end
15
16
 
16
- module FeatureAnnotations
17
- def feature
18
- return nil unless annotations
19
-
20
- annotations[:feature]
21
- end
22
-
23
- def labels
24
- labels = metadata[:appmap]
25
- if labels.is_a?(Array)
26
- labels
27
- elsif labels.is_a?(String) || labels.is_a?(Symbol)
28
- [ labels ]
29
- else
30
- []
31
- end
32
- end
33
-
34
- def feature_group
35
- return nil unless annotations
36
-
37
- annotations[:feature_group]
38
- end
39
-
40
- def annotations
41
- metadata.tap do |md|
42
- description_args_hashes.each do |h|
43
- md.merge! h
44
- end
45
- end
46
- end
47
-
48
- protected
49
-
50
- def metadata
51
- return {} unless example_obj.respond_to?(:metadata)
52
-
53
- example_obj.metadata
54
- end
55
-
56
- def description_args_hashes
57
- return [] unless example_obj.respond_to?(:metadata)
58
-
59
- (example_obj.metadata[:description_args] || []).select { |arg| arg.is_a?(Hash) }
60
- end
61
- end
62
-
63
17
  # ScopeExample and ScopeExampleGroup is a way to handle the weird way that RSpec
64
18
  # stores the nested example names.
65
19
  ScopeExample = Struct.new(:example) do
66
- include FeatureAnnotations
67
-
68
20
  alias_method :example_obj, :example
69
21
 
70
22
  def description?
@@ -83,8 +35,6 @@ module AppMap
83
35
  # As you can see here, the way that RSpec stores the example description and
84
36
  # represents the example group hierarchy is pretty weird.
85
37
  ScopeExampleGroup = Struct.new(:example_group) do
86
- include FeatureAnnotations
87
-
88
38
  alias_method :example_obj, :example_group
89
39
 
90
40
  def description_args
@@ -133,11 +83,17 @@ module AppMap
133
83
  page.driver.options[:http_client].instance_variable_get('@server_url').port
134
84
  end
135
85
 
136
- warn "Starting recording of example #{example}" if AppMap::RSpec::LOG
86
+ warn "Starting recording of example #{example}@#{source_location}" if AppMap::RSpec::LOG
137
87
  @trace = AppMap.tracing.trace
138
88
  @webdriver_port = webdriver_port.()
139
89
  end
140
90
 
91
+ def source_location
92
+ result = example.location_rerun_argument.split(':')[0]
93
+ result = result[2..-1] if result.index('./') == 0
94
+ result
95
+ end
96
+
141
97
  def finish
142
98
  warn "Finishing recording of example #{example}" if AppMap::RSpec::LOG
143
99
 
@@ -148,22 +104,16 @@ module AppMap
148
104
 
149
105
  AppMap::RSpec.add_event_methods @trace.event_methods
150
106
 
151
- class_map = AppMap.class_map(@trace.event_methods, include_source: false)
107
+ class_map = AppMap.class_map(@trace.event_methods, include_source: AppMap.include_source?)
152
108
 
153
109
  description = []
154
110
  scope = ScopeExample.new(example)
155
- feature_group = feature = nil
156
111
 
157
- labels = []
158
112
  while scope
159
- labels += scope.labels
160
113
  description << scope.description
161
- feature ||= scope.feature
162
- feature_group ||= scope.feature_group
163
114
  scope = scope.parent
164
115
  end
165
116
 
166
- labels = labels.map(&:to_s).map(&:strip).reject(&:blank?).map(&:downcase).uniq
167
117
  description.reject!(&:nil?).reject!(&:blank?)
168
118
  default_description = description.last
169
119
  description.reverse!
@@ -177,24 +127,10 @@ module AppMap
177
127
 
178
128
  full_description = normalize.call(description.join(' '))
179
129
 
180
- compute_feature_name = lambda do
181
- return 'unknown' if description.empty?
182
-
183
- feature_description = description.dup
184
- num_tokens = [2, feature_description.length - 1].min
185
- feature_description[0...num_tokens].map(&:strip).join(' ')
186
- end
187
-
188
- feature_group ||= normalize.call(default_description).underscore.gsub('/', '_').humanize
189
- feature_name = feature || compute_feature_name.call if feature_group
190
- feature_name = normalize.call(feature_name) if feature_name
191
-
192
130
  AppMap::RSpec.save full_description,
193
131
  class_map,
194
- events: events,
195
- feature_name: feature_name,
196
- feature_group_name: feature_group,
197
- labels: labels.blank? ? nil : labels
132
+ source_location,
133
+ events: events
198
134
  end
199
135
  end
200
136
 
@@ -227,12 +163,11 @@ module AppMap
227
163
  @event_methods += event_methods
228
164
  end
229
165
 
230
- def save(example_name, class_map, events: nil, feature_name: nil, feature_group_name: nil, labels: nil)
166
+ def save(example_name, class_map, source_location, events: nil, labels: nil)
231
167
  metadata = AppMap::RSpec.metadata.tap do |m|
232
168
  m[:name] = example_name
169
+ m[:source_location] = source_location
233
170
  m[:app] = AppMap.configuration.name
234
- m[:feature] = feature_name if feature_name
235
- m[:feature_group] = feature_group_name if feature_group_name
236
171
  m[:labels] = labels if labels
237
172
  m[:frameworks] ||= []
238
173
  m[:frameworks] << {
@@ -3,7 +3,7 @@
3
3
  module AppMap
4
4
  URL = 'https://github.com/applandinc/appmap-ruby'
5
5
 
6
- VERSION = '0.40.0'
6
+ VERSION = '0.42.1'
7
7
 
8
8
  APPMAP_FORMAT_VERSION = '1.4'
9
9
  end
@@ -8,7 +8,7 @@ describe 'AbstractControllerBase' do
8
8
  FileUtils.rm_rf tmpdir
9
9
  FileUtils.mkdir_p tmpdir
10
10
  cmd = <<~CMD.gsub "\n", ' '
11
- docker-compose run --rm -e APPMAP=true
11
+ docker-compose run --rm -e RAILS_ENV=test -e APPMAP=true
12
12
  -v #{File.absolute_path tmpdir}:/app/tmp app ./bin/rspec #{spec_name}
13
13
  CMD
14
14
  run_cmd cmd, chdir: fixture_dir
@@ -137,7 +137,7 @@ describe 'AbstractControllerBase' do
137
137
  'name' => 'Renderer',
138
138
  'children' => include(hash_including(
139
139
  'name' => 'render',
140
- 'labels' => ['view']
140
+ 'labels' => ['mvc.view']
141
141
  ))
142
142
  ))
143
143
  ))
data/spec/config_spec.rb CHANGED
@@ -7,6 +7,7 @@ require 'appmap/config'
7
7
  describe AppMap::Config, docker: false do
8
8
  it 'loads from a Hash' do
9
9
  config_data = {
10
+ exclude: [],
10
11
  name: 'test',
11
12
  packages: [
12
13
  {
@@ -0,0 +1,15 @@
1
+ class ExcludeTest
2
+ def instance_method
3
+ 'instance_method'
4
+ end
5
+
6
+ class << self
7
+ def singleton_method
8
+ 'singleton_method'
9
+ end
10
+ end
11
+
12
+ def self.cls_method
13
+ 'class_method'
14
+ end
15
+ end
@@ -1,4 +1,7 @@
1
1
  name: rails5_users_app
2
2
  packages:
3
- - path: app
3
+ - path: app/models
4
+ labels: [ mvc.model ]
5
+ - path: app/controllers
6
+ labels: [ mvc.controller ]
4
7
  - gem: sequel
@@ -19,6 +19,9 @@ services:
19
19
  environment:
20
20
  RAILS_ENV:
21
21
  ORM_MODULE:
22
+ PGHOST: pg
23
+ PGPORT: '5432'
24
+ DATABASE_URL: postgres://postgres@pg
22
25
  APPMAP:
23
26
  volumes:
24
27
  - .:/src/app
@@ -1,8 +1,8 @@
1
1
  require 'rails_helper'
2
2
  require 'rack/test'
3
3
 
4
- RSpec.describe Api::UsersController, feature_group: 'Users', type: :controller, appmap: true do
5
- describe 'POST /api/users', feature: 'Create a user' do
4
+ RSpec.describe Api::UsersController, type: :controller do
5
+ describe 'POST /api/users' do
6
6
  describe 'with required parameters' do
7
7
  it 'creates a user' do
8
8
  post :create, params: { login: 'alice', password: 'foobar' }
@@ -16,7 +16,7 @@ RSpec.describe Api::UsersController, feature_group: 'Users', type: :controller,
16
16
  end
17
17
  end
18
18
  end
19
- describe 'GET /api/users', feature: 'List users' do
19
+ describe 'GET /api/users' do
20
20
  before do
21
21
  post :create, params: { login: 'alice' }
22
22
  end
@@ -1,6 +1,6 @@
1
1
  require 'rails_helper'
2
2
 
3
- describe User, feature_group: 'User', appmap: true do
3
+ describe User do
4
4
  # TODO: appmap/rspec doesn't handle shared_examples_for 100% correctly yet.
5
5
  # In my tests, only one of these two tests will be emitted as an appmap.
6
6
  shared_examples_for 'creates the user' do |username|
@@ -11,17 +11,7 @@ describe User, feature_group: 'User', appmap: true do
11
11
  end
12
12
  end
13
13
 
14
- describe 'creation', feature: 'Create a user' do
15
- context 'using shared_examples_for' do
16
- # AppMap.
17
- # context "with username 'alice'" do
18
- # it_should_behave_like 'creates the user', 'alice'
19
- # end
20
- # context "with username 'bob'" do
21
- # it_should_behave_like 'creates the user', 'bob'
22
- # end
23
- end
24
-
14
+ describe 'creation' do
25
15
  # So, instead of shared_examples_for, let's go with a simple method
26
16
  # containing the assertions. The method can be called from within an example.
27
17
  def save_and_verify
@@ -1,5 +1,8 @@
1
1
  name: rails6_users_app
2
2
  packages:
3
- - path: app
3
+ - path: app/models
4
+ labels: [ mvc.model ]
5
+ - path: app/controllers
6
+ labels: [ mvc.controller ]
4
7
  - gem: sequel
5
8
 
@@ -19,6 +19,9 @@ services:
19
19
  environment:
20
20
  RAILS_ENV:
21
21
  ORM_MODULE:
22
+ PGHOST: pg
23
+ PGPORT: '5432'
24
+ DATABASE_URL: postgres://postgres@pg
22
25
  APPMAP:
23
26
  volumes:
24
27
  - .:/src/app
@@ -1,8 +1,8 @@
1
1
  require 'rails_helper'
2
2
  require 'rack/test'
3
3
 
4
- RSpec.describe Api::UsersController, feature_group: 'Users', type: :controller, appmap: true do
5
- describe 'POST /api/users', feature: 'Create a user' do
4
+ RSpec.describe Api::UsersController, type: :controller do
5
+ describe 'POST /api/users' do
6
6
  describe 'with required parameters' do
7
7
  it 'creates a user' do
8
8
  post :create, params: { login: 'alice', password: 'foobar' }
@@ -16,7 +16,7 @@ RSpec.describe Api::UsersController, feature_group: 'Users', type: :controller,
16
16
  end
17
17
  end
18
18
  end
19
- describe 'GET /api/users', feature: 'List users' do
19
+ describe 'GET /api/users' do
20
20
  before do
21
21
  post :create, params: { login: 'alice' }
22
22
  end
@@ -1,6 +1,6 @@
1
1
  require 'rails_helper'
2
2
 
3
- describe User, feature_group: 'User', appmap: true do
3
+ describe User do
4
4
  # TODO: appmap/rspec doesn't handle shared_examples_for 100% correctly yet.
5
5
  # In my tests, only one of these two tests will be emitted as an appmap.
6
6
  shared_examples_for 'creates the user' do |username|
@@ -11,17 +11,7 @@ describe User, feature_group: 'User', appmap: true do
11
11
  end
12
12
  end
13
13
 
14
- describe 'creation', feature: 'Create a user' do
15
- context 'using shared_examples_for' do
16
- # AppMap.
17
- # context "with username 'alice'" do
18
- # it_should_behave_like 'creates the user', 'alice'
19
- # end
20
- # context "with username 'bob'" do
21
- # it_should_behave_like 'creates the user', 'bob'
22
- # end
23
- end
24
-
14
+ describe 'creation' do
25
15
  # So, instead of shared_examples_for, let's go with a simple method
26
16
  # containing the assertions. The method can be called from within an example.
27
17
  def save_and_verify
data/spec/hook_spec.rb CHANGED
@@ -61,6 +61,16 @@ describe 'AppMap class Hooking', docker: false do
61
61
  AppMap.configuration = nil
62
62
  end
63
63
 
64
+ it 'excludes named classes and methods' do
65
+ load 'spec/fixtures/hook/exclude.rb'
66
+ package = AppMap::Config::Package.build_from_path('spec/fixtures/hook/exclude.rb')
67
+ config = AppMap::Config.new('hook_spec', [ package ], %w[ExcludeTest])
68
+ AppMap.configuration = config
69
+
70
+ expect(config.never_hook?(ExcludeTest.new.method(:instance_method))).to be_truthy
71
+ expect(config.never_hook?(ExcludeTest.method(:cls_method))).to be_truthy
72
+ end
73
+
64
74
  it 'parses labels from comments' do
65
75
  _, tracer = invoke_test_file 'spec/fixtures/hook/labels.rb' do
66
76
  ClassWithLabel.new.fn_with_label
@@ -827,7 +837,7 @@ describe 'AppMap class Hooking', docker: false do
827
837
  entry = cm[1][:children][0][:children][0][:children][0]
828
838
  # Sanity check, make sure we got the right one
829
839
  expect(entry[:name]).to eq('secure_compare')
830
- expect(entry[:labels]).to eq(%w[security crypto])
840
+ expect(entry[:labels]).to eq(%w[provider.secure_compare])
831
841
  end
832
842
  end
833
843
 
@@ -5,7 +5,7 @@ describe 'SQL events' do
5
5
  around(:each) do |example|
6
6
  FileUtils.rm_rf tmpdir
7
7
  FileUtils.mkdir_p tmpdir
8
- cmd = "docker-compose run --rm -e ORM_MODULE=#{orm_module} -e APPMAP=true -v #{File.absolute_path tmpdir}:/app/tmp app ./bin/rspec spec/controllers/users_controller_api_spec.rb:#{test_line_number}"
8
+ cmd = "docker-compose run --rm -e ORM_MODULE=#{orm_module} -e RAILS_ENV=test -e APPMAP=true -v #{File.absolute_path tmpdir}:/app/tmp app ./bin/rspec spec/controllers/users_controller_api_spec.rb:#{test_line_number}"
9
9
  run_cmd cmd, chdir: fixture_dir
10
10
 
11
11
  example.run
@@ -1,3 +1,3 @@
1
1
  name: gem_test
2
2
  packages:
3
- - gem: activesupport
3
+ - gem: parser
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'appmap/minitest'
5
+ require 'minitest/autorun'
6
+ require 'parser/current'
7
+
8
+ class ParserTest < ::Minitest::Test
9
+ def test_parser
10
+ Parser::CurrentRuby.parse(File.read(__FILE__))
11
+ end
12
+ end
@@ -2,7 +2,7 @@ require 'rspec'
2
2
  require 'appmap/rspec'
3
3
  require 'hello'
4
4
 
5
- describe Hello, feature_group: 'Saying hello' do
5
+ describe Hello do
6
6
  before do
7
7
  # Trick appmap-ruby into thinking we're a Rails app.
8
8
  stub_const('Rails', double('rails', version: 'fake.0'))
@@ -11,11 +11,11 @@ describe Hello, feature_group: 'Saying hello' do
11
11
  # The order of these examples is important. The tests check the
12
12
  # appmap for 'says hello', and we want another example to get run
13
13
  # before it.
14
- it 'does not say goodbye', feature: 'Speak hello', appmap: true do
14
+ it 'does not say goodbye' do
15
15
  expect(Hello.new.say_hello).not_to eq('Goodbye!')
16
16
  end
17
17
 
18
- it 'says hello', feature: 'Speak hello', appmap: true do
18
+ it 'says hello' do
19
19
  expect(Hello.new.say_hello).to eq('Hello!')
20
20
  end
21
21
  end
@@ -2,7 +2,7 @@ require 'rspec'
2
2
  require 'appmap/rspec'
3
3
  require 'hello'
4
4
 
5
- describe Hello, appmap: true do
5
+ describe Hello do
6
6
  it 'says hello' do
7
7
  expect(Hello.new.say_hello).to eq('Hello!')
8
8
  end
data/test/gem_test.rb CHANGED
@@ -19,14 +19,14 @@ class MinitestTest < Minitest::Test
19
19
  end
20
20
 
21
21
  def test_record_gem
22
- perform_gem_test 'to_param' do
23
- appmap_file = 'tmp/appmap/minitest/To_param_to_param.appmap.json'
22
+ perform_gem_test 'parser' do
23
+ appmap_file = 'tmp/appmap/minitest/Parser_parser.appmap.json'
24
24
  appmap = JSON.parse(File.read(appmap_file))
25
25
  events = appmap['events']
26
26
  assert_equal 2, events.size
27
27
  assert_equal 'call', events.first['event']
28
- assert_equal 'to_param', events.first['method_id']
29
- assert_equal "#{Gem.loaded_specs['activesupport'].gem_dir}/lib/active_support/core_ext/object/to_query.rb", events.first['path']
28
+ assert_equal 'default_parser', events.first['method_id']
29
+ assert_equal "#{Gem.loaded_specs['parser'].gem_dir}/lib/parser/base.rb", events.first['path']
30
30
  assert_equal 'return', events.second['event']
31
31
  assert_equal 1, events.second['parent_id']
32
32
  end
@@ -30,9 +30,8 @@ class MinitestTest < Minitest::Test
30
30
  assert_equal 'minitest_recorder', metadata['app']
31
31
  assert_equal 'minitest', metadata['recorder']['name']
32
32
  assert_equal 'ruby', metadata['language']['name']
33
- assert_equal 'Hello', metadata['feature_group']
34
- assert_equal 'hello', metadata['feature']
35
33
  assert_equal 'Hello hello', metadata['name']
34
+ assert_equal 'test/hello_test.rb:9', metadata['source_location']
36
35
  end
37
36
  end
38
37
  end
data/test/rspec_test.rb CHANGED
@@ -28,8 +28,6 @@ class RSpecTest < Minitest::Test
28
28
  assert_includes appmap.keys, 'metadata'
29
29
  metadata = appmap['metadata']
30
30
  assert_equal 'Inventory', metadata['name']
31
- assert_includes metadata.keys, 'labels'
32
- assert_equal metadata['labels'], %w[inventory]
33
31
  end
34
32
  end
35
33
 
@@ -42,9 +40,8 @@ class RSpecTest < Minitest::Test
42
40
  assert_equal AppMap::APPMAP_FORMAT_VERSION, appmap['version']
43
41
  assert_includes appmap.keys, 'metadata'
44
42
  metadata = appmap['metadata']
45
- assert_equal 'Saying hello', metadata['feature_group']
46
- assert_equal 'Speak hello', metadata['feature']
47
43
  assert_equal 'Hello says hello', metadata['name']
44
+ assert_equal 'spec/decorated_hello_spec.rb', metadata['source_location']
48
45
  assert_includes metadata.keys, 'client'
49
46
  assert_equal({ name: 'appmap', url: AppMap::URL, version: AppMap::VERSION }.stringify_keys, metadata['client'])
50
47
  assert_includes metadata.keys, 'recorder'
@@ -63,8 +60,6 @@ class RSpecTest < Minitest::Test
63
60
  appmap = JSON.parse(File.read(appmap_file))
64
61
  assert_includes appmap.keys, 'metadata'
65
62
  metadata = appmap['metadata']
66
- assert_equal 'Hello', metadata['feature_group']
67
- assert_equal 'Hello', metadata['feature']
68
63
  assert_equal 'Hello says hello', metadata['name']
69
64
  end
70
65
  end
@@ -76,7 +71,6 @@ class RSpecTest < Minitest::Test
76
71
  appmap = JSON.parse(File.read(appmap_file))
77
72
  assert_includes appmap.keys, 'metadata'
78
73
  metadata = appmap['metadata']
79
- assert_equal %w[hello speak], metadata['labels'].sort
80
74
  end
81
75
  end
82
76
  end
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.40.0
4
+ version: 0.42.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Gilpin
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-29 00:00:00.000000000 Z
11
+ date: 2021-03-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -386,6 +386,7 @@ files:
386
386
  - spec/fixtures/hook/compare.rb
387
387
  - spec/fixtures/hook/constructor.rb
388
388
  - spec/fixtures/hook/exception_method.rb
389
+ - spec/fixtures/hook/exclude.rb
389
390
  - spec/fixtures/hook/instance_method.rb
390
391
  - spec/fixtures/hook/labels.rb
391
392
  - spec/fixtures/hook/singleton_method.rb
@@ -547,7 +548,6 @@ files:
547
548
  - spec/railtie_spec.rb
548
549
  - spec/record_sql_rails_pg_spec.rb
549
550
  - spec/remote_recording_spec.rb
550
- - spec/rspec_feature_metadata_spec.rb
551
551
  - spec/spec_helper.rb
552
552
  - spec/util_spec.rb
553
553
  - test/cli_test.rb
@@ -572,7 +572,7 @@ files:
572
572
  - test/fixtures/cucumber_recorder/lib/hello.rb
573
573
  - test/fixtures/gem_test/Gemfile
574
574
  - test/fixtures/gem_test/appmap.yml
575
- - test/fixtures/gem_test/test/to_param_test.rb
575
+ - test/fixtures/gem_test/test/parser_test.rb
576
576
  - test/fixtures/minitest_recorder/Gemfile
577
577
  - test/fixtures/minitest_recorder/appmap.yml
578
578
  - test/fixtures/minitest_recorder/lib/hello.rb
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rails_spec_helper'
4
-
5
- describe 'RSpec feature and feature group metadata' do
6
- include_examples 'Rails app pg database', 'spec/fixtures/rails5_users_app' do
7
- around(:each) do |example|
8
- FileUtils.rm_rf tmpdir
9
- FileUtils.mkdir_p tmpdir
10
- cmd = "docker-compose run --rm -e APPMAP=true -v #{File.absolute_path(tmpdir).shellescape}:/app/tmp app ./bin/rspec spec/models/user_spec.rb"
11
- run_cmd cmd, chdir: fixture_dir
12
-
13
- example.run
14
- end
15
-
16
- let(:tmpdir) { 'tmp/spec/RSpec feature and feature group metadata' }
17
- let(:appmap_json) { File.join(tmpdir, %(appmap/rspec/User_creation_creates_charles.appmap.json)) }
18
-
19
- describe do
20
- it 'are recorded in the appmap' do
21
- expect(File).to exist(appmap_json)
22
- appmap = JSON.parse(File.read(appmap_json)).to_yaml
23
-
24
- expect(appmap).to include(<<-METADATA.strip)
25
- feature: Create a user
26
- feature_group: User
27
- METADATA
28
- end
29
- end
30
- end
31
- end
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require 'appmap/minitest'
5
- require 'minitest/autorun'
6
- require 'active_support'
7
- require 'active_support/core_ext'
8
-
9
- class ToParamTest < ::Minitest::Test
10
- def test_to_param
11
- # record use of a core extension
12
- assert_equal 'my+id', 'my+id'.to_param
13
- end
14
- end