appmap 0.47.0 → 0.49.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: f1f2b43daf0437ceb6073a64272148ca9168c4ac781c01e2e73c36259a3bcc7b
4
- data.tar.gz: 2a7275d2be15d2bfcf0c2dfc898807212840506c3692232b2d049bc771a4a3b9
3
+ metadata.gz: '02814a0f9d0927d19c2d341a22d8f8ec66b83bc022833a09a7777ce81ec4edfc'
4
+ data.tar.gz: 11b2786d88e360fc78e046675ae799e7106625dd02b4a3070c3835bfe8feea05
5
5
  SHA512:
6
- metadata.gz: 5000bdc938facaa5e181de6e3d6023c6a1b1a1e48a260545ae3245236bf271e22ab6462c16d1c73fb44b4e9bc4e6533f8be12428e2d7ff9e1c06f65ec1992bf7
7
- data.tar.gz: 10db3cd34b1f9eb5f81070607d12b6a714b12735ac25828df4c6a3e50d0efa354ad9ff9206bc42cd2d7ccb61ffef83f663d41b3896928f0fd9bdd1e53ed4e543
6
+ metadata.gz: c852ae464d52f9f29ef9222fa1627f99b4f5baf1907b1cd54cbfb969ff2323cb19e0d5b53bad1356518ec21a07243ea71237ad831a62ffe825a2601cca1a5cf7
7
+ data.tar.gz: 49c5ba0d0c20d8bee6e4c768544ab6abe0f0f0d71a1a59bc04fcf867e93bc743eda4cc94f7505c0a1194c07e95e2b8e7f875a42b16aa3da2a21068a61ba42a10
data/.dockerignore CHANGED
@@ -2,4 +2,3 @@ vendor
2
2
  node_modules
3
3
  spec/fixtures/rails*_users_app
4
4
  spec/fixtures/rack_users_app
5
-
data/CHANGELOG.md CHANGED
@@ -1,3 +1,40 @@
1
+ # [0.49.0](https://github.com/applandinc/appmap-ruby/compare/v0.48.2...v0.49.0) (2021-06-16)
2
+
3
+
4
+ ### Features
5
+
6
+ * Add refinement to the labels ([6a93396](https://github.com/applandinc/appmap-ruby/commit/6a93396ba73f1b3ed21b4e9e15a2c271af04d866))
7
+
8
+ ## [0.48.2](https://github.com/applandinc/appmap-ruby/compare/v0.48.1...v0.48.2) (2021-05-26)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Correct the method-hooking logic to capture some missing model methods ([be529bd](https://github.com/applandinc/appmap-ruby/commit/be529bdce7d4fdf9f1a2fdd32259d792f29f4f13))
14
+
15
+ ## [0.48.1](https://github.com/applandinc/appmap-ruby/compare/v0.48.0...v0.48.1) (2021-05-25)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * Account for bundle path when normalizing source path ([095c278](https://github.com/applandinc/appmap-ruby/commit/095c27818fc8ae8dfa39b30516d37c6dfd642d9c))
21
+ * Scan exception messages for non-UTF8 characters ([3dcaeae](https://github.com/applandinc/appmap-ruby/commit/3dcaeae44da5e40e432eda41caf5b9ebff5bea12))
22
+
23
+ # [0.48.0](https://github.com/applandinc/appmap-ruby/compare/v0.47.1...v0.48.0) (2021-05-19)
24
+
25
+
26
+ ### Features
27
+
28
+ * Hook the code only when APPMAP=true ([dd9e383](https://github.com/applandinc/appmap-ruby/commit/dd9e383024d1d9205a617d46bd64b90820035533))
29
+ * Remove server process recording from doc and tests ([383ba0a](https://github.com/applandinc/appmap-ruby/commit/383ba0ad444922a0a85409477d11bc7ed06a9160))
30
+
31
+ ## [0.47.1](https://github.com/applandinc/appmap-ruby/compare/v0.47.0...v0.47.1) (2021-05-13)
32
+
33
+
34
+ ### Bug Fixes
35
+
36
+ * Add the proper template function hooks for Rails 6.0.7 ([175f489](https://github.com/applandinc/appmap-ruby/commit/175f489acbaed77ad52a18d805e4b6eeae1abfdb))
37
+
1
38
  # [0.47.0](https://github.com/applandinc/appmap-ruby/compare/v0.46.0...v0.47.0) (2021-05-13)
2
39
 
3
40
 
data/README.md CHANGED
@@ -1,17 +1,6 @@
1
1
 
2
2
  - [About](#about)
3
- - [Supported versions](#supported-versions)
4
- - [Installation](#installation)
5
- - [Configuration](#configuration)
6
- - [Labels](#labels)
7
- - [Running](#running)
8
- - [RSpec](#rspec)
9
- - [Minitest](#minitest)
10
- - [Cucumber](#cucumber)
11
- - [Remote recording](#remote-recording)
12
- - [AppMap for VSCode](#appmap-for-vscode)
13
- - [AppMap Swagger](#appmap-swagger)
14
- - [Uploading AppMaps](#uploading-appmaps)
3
+ - [Usage](#usage)
15
4
  - [Development](#development)
16
5
  - [Internal architecture](#internal-architecture)
17
6
  - [Running tests](#running-tests)
@@ -19,7 +8,6 @@
19
8
  - [`test/fixtures`](#testfixtures)
20
9
  - [`spec/fixtures`](#specfixtures)
21
10
 
22
-
23
11
  # About
24
12
 
25
13
  `appmap-ruby` is a Ruby Gem for recording
@@ -29,342 +17,9 @@
29
17
  SHA, labels, etc). It's more granular than a performance profile, but it's less
30
18
  granular than a full debug trace. It's designed to be optimal for understanding the design intent and structure of code and key data flows.
31
19
 
32
- There are several ways to record AppMaps of your Ruby program using the `appmap` gem:
33
-
34
- * Run your tests (RSpec, Minitest, Cucumber) with the environment variable `APPMAP=true`. An AppMap will be generated for each spec.
35
- * Run your application server with AppMap remote recording enabled, and use the [AppLand
36
- browser extension](https://github.com/applandinc/appland-browser-extension) to start,
37
- stop, and upload recordings.
38
- * Wrap some code in an `AppMap.record` block, which returns JSON containing the code execution trace.
39
-
40
- Once you have made a recording, there are two ways to view automatically generated diagrams of the AppMaps.
41
-
42
- The first option is to load the diagrams directly in your IDE, using the [AppMap extension for VSCode](https://marketplace.visualstudio.com/items?itemName=appland.appmap).
43
-
44
- The second option is to upload them to the [AppLand server](https://app.land) using the [AppLand CLI](https://github.com/applandinc/appland-cli/releases).
45
-
46
- ### Supported versions
47
-
48
- * Ruby 2.5, 2.6, 2.7
49
- * Rails 5, 6
50
-
51
- Support for new versions is added frequently, please check back regularly for updates.
52
-
53
- # Installation
54
-
55
- <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>
56
-
57
-
58
- 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:
59
-
60
- ```
61
- source 'https://rubygems.org'
62
- git_source(:github) { |repo| "https://github.com/#{repo}.git" }
63
-
64
- # Optional rubRuby version
65
- # ruby '2.7.2'
66
-
67
- group :development, :test do
68
- gem 'appmap'
69
- end
70
- ```
71
-
72
- Install with `bundle install`, as usual.
73
-
74
- 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*.
75
-
76
- **Railtie**
77
-
78
- If you are using Ruby on Rails, require the railtie after Rails is loaded.
79
-
80
- ```
81
- # application.rb is a good place to do this, along with all the other railties.
82
- # Don't require the railtie in environments that don't bundle the appmap gem.
83
- require 'appmap/railtie' if defined?(AppMap).
84
- ```
85
-
86
- **application.rb**
87
-
88
- Add this line to *application.rb*, to enable server recording with `APPMAP_RECORD=true`:
89
-
90
- ```ruby
91
- module MyApp
92
- class Application < Rails::Application
93
- ...
94
-
95
- config.appmap.enabled = true if ENV['APPMAP_RECORD']
96
-
97
- ...
98
- end
99
- end
100
- ```
101
-
102
- # Configuration
103
-
104
- When you run your program, the `appmap` gem reads configuration settings from `appmap.yml`. Here's a sample configuration
105
- file for a typical Rails project:
106
-
107
- ```yaml
108
- # 'name' should generally be the same as the code repo name.
109
- name: my_project
110
- packages:
111
- - path: app/controllers
112
- - path: app/models
113
- # Exclude sub-paths within the package path
114
- exclude:
115
- - concerns/accessor
116
- - path: app/jobs
117
- - path: app/helpers
118
- # Include the gems that you want to see in the dependency maps.
119
- # These are just examples.
120
- - gem: activerecord
121
- - gem: devise
122
- - gem: aws-sdk
123
- - gem: will_paginate
124
- # Global exclusion of a class name
125
- exclude:
126
- - MyClass
127
- - MyClass#my_instance_method
128
- - MyClass.my_class_method
129
- functions:
130
- - packages: myapp
131
- class: ControllerHelper
132
- function: logged_in_user
133
- labels: [ authentication ]
134
- ```
135
-
136
- * **name** Provides the project name (required)
137
- * **packages** A list of source code directories which should be recorded.
138
- * **exclude** A list of classes and/or methods to definitively exclude from recording.
139
- * **functions** A list of specific functions, scoped by package and class, to record.
140
-
141
- **packages**
142
-
143
- Each entry in the `packages` list is a YAML object which has the following keys:
144
-
145
- * **path** The path to the source code directory. The path may be relative to the current working directory, or it may
146
- be an absolute path.
147
- * **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*.
148
- * **exclude** A list of files and directories which will be ignored. By default, all modules, classes and public
149
- functions are inspected. See also: global `exclude` list.
150
- * **shallow** When set to `true`, only the first function call entry into a package will be recorded. Subsequent function calls within
151
- the same package are not recorded unless code execution leaves the package and re-enters it. Default: `true` when using `gem`,
152
- `false` when using `path`.
153
-
154
- **exclude**
155
-
156
- Optional list of fully qualified class and method names. Separate class and method names with period (`.`) for class methods and hash (`#`) for instance methods.
157
-
158
- **functions**
159
-
160
- Optional list of `class, function` pairs. The `package` name is used to place the function within the class map, and does not have to match
161
- the folder or gem name. The primary use of `functions` is to apply specific labels to functions whose source code is not accessible (e.g., it's in a Gem).
162
- For functions which are part of the application code, use `@label` or `@labels` in code comments to apply labels.
163
-
164
- # Labels
165
-
166
- 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.
167
-
168
- You can apply function labels using source code comments in your Ruby code. To apply a labels to a function, add a `@label` or `@labels` line to the comment which immediately precedes a function.
169
-
170
- For example, if you add this comment to your source code:
171
-
172
- ```ruby
173
- class ApiKey
174
- # @labels provider.authentication security
175
- def authenticate(key)
176
- # logic to verify the key here...
177
- end
178
- end
179
- ```
180
-
181
- Then the AppMap metadata section for this function will include:
182
-
183
- ```json
184
- {
185
- "name": "authenticate",
186
- "type": "function",
187
- "labels": [ "provider.authentication", "security" ]
188
- }
189
- ```
190
-
191
-
192
- # Running
193
-
194
- ## RSpec
195
-
196
- To record RSpec tests, follow these additional steps:
197
-
198
- 1) Require `appmap/rspec` in your `spec_helper.rb` before any other classes are loaded.
199
-
200
- ```ruby
201
- require 'appmap/rspec'
202
- ```
203
-
204
- Note that `spec_helper.rb` in a Rails project typically loads the application's classes this way:
205
-
206
- ```ruby
207
- require File.expand_path("../../config/environment", __FILE__)
208
- ```
209
-
210
- and `appmap/rspec` must be required before this:
211
-
212
- ```ruby
213
- require 'appmap/rspec'
214
- require File.expand_path("../../config/environment", __FILE__)
215
- ```
216
-
217
- 2) Run the tests with the environment variable `APPMAP=true`:
218
-
219
- ```sh-session
220
- $ APPMAP=true bundle exec rspec
221
- ```
222
-
223
- Each RSpec test will output an AppMap file into the directory `tmp/appmap/rspec`. For example:
224
-
225
- ```
226
- $ find tmp/appmap/rspec
227
- Hello_says_hello_when_prompted.appmap.json
228
- ```
229
-
230
- ## Minitest
231
-
232
- To record Minitest tests, follow these additional steps:
233
-
234
- 1) Require `appmap/minitest` in `test_helper.rb`
235
-
236
- ```ruby
237
- require 'appmap/minitest'
238
- ```
239
-
240
- Note that `test_helper.rb` in a Rails project typically loads the application's classes this way:
241
-
242
- ```ruby
243
- require_relative '../config/environment'
244
- ```
245
-
246
- and `appmap/minitest` must be required before this:
247
-
248
- ```ruby
249
- require 'appmap/minitest'
250
- require_relative '../config/environment'
251
- ```
252
-
253
- 2) Run your tests as you normally would with the environment variable `APPMAP=true`. For example:
254
-
255
- ```
256
- $ APPMAP=true bundle exec rake test
257
- ```
258
-
259
- or
260
-
261
- ```
262
- $ APPMAP=true bundle exec ruby -Ilib -Itest test/*_test.rb
263
- ```
264
-
265
- Each Minitest test will output an AppMap file into the directory `tmp/appmap/minitest`. For example:
266
-
267
- ```
268
- $ find tmp/appmap/minitest
269
- Hello_says_hello_when_prompted.appmap.json
270
- ```
271
-
272
- ## Cucumber
273
-
274
- To record Cucumber tests, follow these additional steps:
275
-
276
- 1) Require `appmap/cucumber` in `support/env.rb`:
277
-
278
- ```ruby
279
- require 'appmap/cucumber'
280
- ```
281
-
282
- Be sure to require it before `config/environment` is required.
283
-
284
- 2) Create an `Around` hook in `support/hooks.rb` to record the scenario:
285
-
286
-
287
- ```ruby
288
- if AppMap::Cucumber.enabled?
289
- Around('not @appmap-disable') do |scenario, block|
290
- appmap = AppMap.record do
291
- block.call
292
- end
293
-
294
- AppMap::Cucumber.write_scenario(scenario, appmap)
295
- end
296
- end
297
- ```
298
-
299
- 3) Run the tests with the environment variable `APPMAP=true`:
300
-
301
- ```sh-session
302
- $ APPMAP=true bundle exec cucumber
303
- ```
304
-
305
- Each Cucumber test will output an AppMap file into the directory `tmp/appmap/cucumber`. For example:
306
-
307
- ```
308
- $ find tmp/appmap/cucumber
309
- Hello_Says_hello_when_prompted.appmap.json
310
- ```
311
-
312
- ## Remote recording
313
-
314
- To manually record ad-hoc AppMaps of your Ruby app, use AppMap remote recording.
315
-
316
- 1. Add the AppMap remote recording middleware. For example, in `config/initializers/appmap_remote_recording.rb`:
317
-
318
- ```ruby
319
- if defined?(AppMap)
320
- require 'appmap/middleware/remote_recording'
321
-
322
- Rails.application.config.middleware.insert_after \
323
- Rails::Rack::Logger,
324
- AppMap::Middleware::RemoteRecording
325
- end
326
- ```
327
-
328
- 2. (optional) Download and unpack the [AppLand browser extension](https://github.com/applandinc/appland-browser-extension). Install into Chrome using `chrome://extensions/`. Turn on "Developer Mode" and then load the extension using the "Load unpacked" button.
329
-
330
- 3. Start your Rails application server, with `APPMAP_RECORD=true`. For example:
331
-
332
- ```sh-session
333
- $ APPMAP_RECORD=true bundle exec rails server
334
- ```
335
-
336
- 4. Start the recording
337
-
338
- Option 1: Open the AppLand browser extension and push `Start`.
339
- Option 2: `curl -XPOST localhost:3000/_appmap/record` (be sure and get the port number right)
340
-
341
- 5. Use your app. For example, perform a login flow, or run through a manual UI test.
342
-
343
- 6. Finish the recording.
344
-
345
- Option 1: Open the AppLand browser extension and push `Stop`. The recording will be transferred to the AppLand website and opened in your browser.
346
- Option 2: `curl -XDELETE localhost:3000/_appmap/record > recording.appmap.json` - Saves the recording as a local file.
347
-
348
-
349
- # AppMap for VSCode
350
-
351
- The [AppMap extension for VSCode](https://marketplace.visualstudio.com/items?itemName=appland.appmap) helps you navigate your code more efficiently with interactive, accurate software architecture diagrams right in your IDE. In less than two minutes you can go from installing the AppMap extension to exploring maps of your code's architecture. AppMap helps you:
352
-
353
- * Onboard to code architecture, with no extra work for the team
354
- * Conduct code and design reviews using live and accurate data
355
- * Troubleshoot hard-to-understand bugs using a "top-down" approach.
356
-
357
- Each interactive diagram links directly to the source code, and the information is easy to share.
358
-
359
- # AppMap Swagger
360
-
361
- [appmap_swagger](https://github.com/applandinc/appmap_swagger-ruby) is a tool to generate Swagger files from AppMap data. With `appmap_swagger`, you can add Swagger to your Ruby or Ruby on Rails project, with no need to write or modify code. Use the Swagger UI to interact with your web services API as you build it, and use diffs of Swagger to perform code review of web service changes.
362
-
363
- # Uploading AppMaps
364
-
365
- [https://app.land](https://app.land) can be used to store, analyze, and share AppMaps.
20
+ # Usage
366
21
 
367
- For instructions on uploading, see the documentation of the [AppLand CLI](https://github.com/applandinc/appland-cli).
22
+ Visit the [AppMap for Ruby](https://appland.com/docs/reference/appmap-ruby.html) reference page on AppLand.com for a complete reference guide.
368
23
 
369
24
  # Development
370
25
  [![Build Status](https://travis-ci.com/applandinc/appmap-ruby.svg?branch=master)](https://travis-ci.com/applandinc/appmap-ruby)
data/lib/appmap.rb CHANGED
@@ -98,4 +98,4 @@ module AppMap
98
98
  end
99
99
 
100
100
  require 'appmap/railtie' if defined?(::Rails::Railtie)
101
- AppMap.initialize unless ENV['APPMAP_INITIALIZE'] == 'false'
101
+ AppMap.initialize if ENV['APPMAP'] == 'true'
data/lib/appmap/config.rb CHANGED
@@ -85,18 +85,7 @@ module AppMap
85
85
  end
86
86
  end
87
87
 
88
- Function = Struct.new(:package, :cls, :labels, :function_names) do # :nodoc:
89
- def to_h
90
- {
91
- package: package,
92
- class: cls,
93
- labels: labels,
94
- functions: function_names.map(&:to_sym)
95
- }.compact
96
- end
97
- end
98
- private_constant :Function
99
-
88
+ # Identifies specific methods within a package which should be hooked.
100
89
  class TargetMethods # :nodoc:
101
90
  attr_reader :method_names, :package
102
91
 
@@ -118,28 +107,93 @@ module AppMap
118
107
  end
119
108
  private_constant :TargetMethods
120
109
 
121
- OPENSSL_PACKAGES = ->(labels) { Package.build_from_path('openssl', package_name: 'openssl', labels: labels) }
110
+ # Function represents a specific function configured for hooking by the +functions+
111
+ # entry in appmap.yml. When the Config is initialized, each Function is converted into
112
+ # a Package and TargetMethods. It's called a Function rather than a Method, because Function
113
+ # is the AppMap terminology.
114
+ Function = Struct.new(:package, :cls, :labels, :function_names) do # :nodoc:
115
+ def to_h
116
+ {
117
+ package: package,
118
+ class: cls,
119
+ labels: labels,
120
+ functions: function_names.map(&:to_sym)
121
+ }.compact
122
+ end
123
+ end
124
+ private_constant :Function
122
125
 
123
- # Methods that should always be hooked, with their containing
124
- # package and labels that should be applied to them.
125
- HOOKED_METHODS = {
126
- 'ActionView::Renderer' => TargetMethods.new(:render, Package.build_from_gem('actionview', shallow: false, package_name: 'action_view', labels: %w[mvc.view], optional: true).tap do |package|
127
- package.handler_class = AppMap::Handler::Rails::Template::RenderHandler if package
128
- end),
129
- 'ActionView::Resolver' => TargetMethods.new(%i[find_all find_all_anywhere], Package.build_from_gem('actionview', shallow: false, package_name: 'action_view', labels: %w[mvc.template.resolver], optional: true).tap do |package|
130
- package.handler_class = AppMap::Handler::Rails::Template::ResolverHandler if package
131
- end),
132
- 'ActionDispatch::Request::Session' => TargetMethods.new(%i[destroy [] dig values []= clear update delete fetch merge], Package.build_from_gem('actionpack', shallow: false, package_name: 'action_dispatch', labels: %w[http.session], optional: true)),
133
- 'ActionDispatch::Cookies::CookieJar' => TargetMethods.new(%i[[]= clear update delete recycle], Package.build_from_gem('actionpack', shallow: false, package_name: 'action_dispatch', labels: %w[http.cookie], optional: true)),
134
- 'ActionDispatch::Cookies::EncryptedCookieJar' => TargetMethods.new(%i[[]=], Package.build_from_gem('actionpack', shallow: false, package_name: 'action_dispatch', labels: %w[http.cookie crypto.encrypt], optional: true)),
135
- 'CanCan::ControllerAdditions' => TargetMethods.new(%i[authorize! can? cannot?], Package.build_from_gem('cancancan', shallow: false, labels: %w[security.authorization], optional: true)),
136
- 'CanCan::Ability' => TargetMethods.new(%i[authorize!], Package.build_from_gem('cancancan', shallow: false, labels: %w[security.authorization], optional: true)),
137
- 'ActionController::Instrumentation' => [
138
- TargetMethods.new(%i[process_action send_file send_data redirect_to], Package.build_from_gem('actionpack', shallow: false, package_name: 'action_controller', labels: %w[mvc.controller], optional: true))
139
- ]
140
- }.freeze
126
+ ClassTargetMethods = Struct.new(:cls, :target_methods) # :nodoc:
127
+ private_constant :ClassTargetMethods
128
+
129
+ MethodHook = Struct.new(:cls, :method_names, :labels) # :nodoc:
130
+ private_constant :MethodHook
131
+
132
+ class << self
133
+ def package_hooks(gem_name, methods, handler_class: nil, package_name: nil)
134
+ Array(methods).map do |method|
135
+ package = Package.build_from_gem(gem_name, package_name: package_name, labels: method.labels, shallow: false, optional: true)
136
+ next unless package
137
+
138
+ package.handler_class = handler_class if handler_class
139
+ ClassTargetMethods.new(method.cls, TargetMethods.new(Array(method.method_names), package))
140
+ end.compact
141
+ end
141
142
 
142
- BUILTIN_METHODS = {
143
+ def method_hook(cls, method_names, labels)
144
+ MethodHook.new(cls, method_names, labels)
145
+ end
146
+ end
147
+
148
+ # Hook well-known functions. When a function configured here is available in the bundle, it will be hooked with the
149
+ # predefined labels specified here. If any of these hooks are not desired, they can be disabled in the +exclude+ section
150
+ # of appmap.yml.
151
+ METHOD_HOOKS = [
152
+ package_hooks('actionview',
153
+ [
154
+ method_hook('ActionView::Renderer', :render, %w[mvc.view]),
155
+ method_hook('ActionView::TemplateRenderer', :render, %w[mvc.view]),
156
+ method_hook('ActionView::PartialRenderer', :render, %w[mvc.view])
157
+ ],
158
+ handler_class: AppMap::Handler::Rails::Template::RenderHandler,
159
+ package_name: 'action_view'
160
+ ),
161
+ package_hooks('actionview',
162
+ [
163
+ method_hook('ActionView::Resolver', %i[find_all find_all_anywhere], %w[mvc.template.resolver])
164
+ ],
165
+ handler_class: AppMap::Handler::Rails::Template::ResolverHandler,
166
+ package_name: 'action_view'
167
+ ),
168
+ package_hooks('actionpack',
169
+ [
170
+ method_hook('ActionDispatch::Request::Session', %i[[] dig values fetch], %w[http.session.read]),
171
+ method_hook('ActionDispatch::Request::Session', %i[destroy[]= clear update delete merge], %w[http.session.write]),
172
+ method_hook('ActionDispatch::Cookies::CookieJar', %i[[]= clear update delete recycle], %w[http.session.read]),
173
+ method_hook('ActionDispatch::Cookies::CookieJar', %i[[]= clear update delete recycle], %w[http.session.write]),
174
+ method_hook('ActionDispatch::Cookies::EncryptedCookieJar', %i[[]= clear update delete recycle], %w[http.cookie crypto.encrypt])
175
+ ],
176
+ package_name: 'action_dispatch'
177
+ ),
178
+ package_hooks('cancancan',
179
+ [
180
+ method_hook('CanCan::ControllerAdditions', %i[authorize! can? cannot?], %w[security.authorization]),
181
+ method_hook('CanCan::Ability', %i[authorize?], %w[security.authorization])
182
+ ]
183
+ ),
184
+ package_hooks('actionpack',
185
+ [
186
+ method_hook('ActionController::Instrumentation', %i[process_action send_file send_data redirect_to], %w[mvc.controller])
187
+ ],
188
+ package_name: 'action_controller'
189
+ )
190
+ ].flatten.freeze
191
+
192
+ OPENSSL_PACKAGES = ->(labels) { Package.build_from_path('openssl', package_name: 'openssl', labels: labels) }
193
+
194
+ # Hook functions which are builtin to Ruby. Because they are builtins, they may be loaded before appmap.
195
+ # Therefore, we can't rely on TracePoint to report the loading of this code.
196
+ BUILTIN_HOOKS = {
143
197
  'OpenSSL::PKey::PKey' => TargetMethods.new(:sign, OPENSSL_PACKAGES.(%w[crypto.pkey])),
144
198
  'OpenSSL::X509::Request' => TargetMethods.new(%i[sign verify], OPENSSL_PACKAGES.(%w[crypto.x509])),
145
199
  'OpenSSL::PKCS5' => TargetMethods.new(%i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGES.(%w[crypto.pkcs5])),
@@ -161,31 +215,37 @@ module AppMap
161
215
  # This is happening: Method send_command not found on Net::IMAP
162
216
  # 'Net::IMAP' => TargetMethods.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[protocol.email.imap])),
163
217
  # 'Marshal' => TargetMethods.new(%i[dump load], Package.build_from_path('marshal', labels: %w[format.marshal])),
164
- 'Psych' => TargetMethods.new(%i[dump dump_stream load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml])),
165
- 'JSON::Ext::Parser' => TargetMethods.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[format.json])),
166
- 'JSON::Ext::Generator::State' => TargetMethods.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json])),
218
+ 'Psych' => [
219
+ TargetMethods.new(%i[load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml.parse])),
220
+ TargetMethods.new(%i[dump dump_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml.generate])),
221
+ ],
222
+ 'JSON::Ext::Parser' => TargetMethods.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[format.json.parse])),
223
+ 'JSON::Ext::Generator::State' => TargetMethods.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json.generate])),
167
224
  }.freeze
168
225
 
169
- attr_reader :name, :packages, :exclude, :hooked_methods, :builtin_methods
226
+ attr_reader :name, :packages, :exclude, :hooked_methods, :builtin_hooks
170
227
 
171
228
  def initialize(name, packages, exclude: [], functions: [])
172
229
  @name = name
173
230
  @packages = packages
174
- @hook_paths = packages.map(&:path)
231
+ @hook_paths = Set.new(packages.map(&:path))
175
232
  @exclude = exclude
176
- @builtin_methods = BUILTIN_METHODS
233
+ @builtin_hooks = BUILTIN_HOOKS
177
234
  @functions = functions
178
- @hooked_methods = HOOKED_METHODS.dup
235
+
236
+ @hooked_methods = METHOD_HOOKS.each_with_object(Hash.new { |h,k| h[k] = [] }) do |cls_target_methods, hooked_methods|
237
+ hooked_methods[cls_target_methods.cls] << cls_target_methods.target_methods
238
+ end
239
+
179
240
  functions.each do |func|
180
241
  package_options = {}
181
242
  package_options[:labels] = func.labels if func.labels
182
- @hooked_methods[func.cls] ||= []
183
243
  @hooked_methods[func.cls] << TargetMethods.new(func.function_names, Package.build_from_path(func.package, package_options))
184
244
  end
185
245
 
186
246
  @hooked_methods.each_value do |hooks|
187
247
  Array(hooks).each do |hook|
188
- @hook_paths << hook.package.path if hook.package
248
+ @hook_paths << hook.package.path
189
249
  end
190
250
  end
191
251
  end
data/lib/appmap/event.rb CHANGED
@@ -213,7 +213,7 @@ module AppMap
213
213
  exception_backtrace = next_exception.backtrace_locations.try(:[], 0)
214
214
  exceptions << {
215
215
  class: best_class_name(next_exception),
216
- message: next_exception.message,
216
+ message: display_string(next_exception.message),
217
217
  object_id: next_exception.__id__,
218
218
  path: exception_backtrace&.path,
219
219
  lineno: exception_backtrace&.lineno
@@ -105,12 +105,18 @@ module AppMap
105
105
  # If so, populate the template path. In all cases, add a TemplateMethod so that the
106
106
  # template will be recorded in the classMap.
107
107
  def handle_return(call_event_id, elapsed, return_value, exception)
108
- warn "Resolver return: #{return_value.inspect}" if LOG
109
-
110
108
  renderer = Array(Thread.current[TEMPLATE_RENDERER]).last
111
- path = Array(return_value).first&.inspect
109
+ path_obj = Array(return_value).first
110
+
111
+ warn "Resolver return: #{path_obj}" if LOG
112
112
 
113
- if path
113
+ if path_obj
114
+ path = if path_obj.respond_to?(:identifier) && path_obj.inspect.index('#<')
115
+ path_obj.identifier
116
+ else
117
+ path_obj.inspect
118
+ end
119
+ path = path[Dir.pwd.length + 1..-1] if path.index(Dir.pwd) == 0
114
120
  AppMap.tracing.record_method(TemplateMethod.new(path))
115
121
  renderer.path ||= path if renderer
116
122
  end
data/lib/appmap/hook.rb CHANGED
@@ -36,7 +36,7 @@ module AppMap
36
36
 
37
37
  def initialize(config)
38
38
  @config = config
39
- @trace_locations = []
39
+ @trace_enabled = []
40
40
  # Paths that are known to be non-tracing
41
41
  @notrace_paths = Set.new
42
42
  end
@@ -47,10 +47,8 @@ module AppMap
47
47
 
48
48
  hook_builtins
49
49
 
50
- @trace_begin = TracePoint.new(:class, &method(:trace_class))
51
50
  @trace_end = TracePoint.new(:end, &method(:trace_end))
52
-
53
- @trace_begin.enable(&block)
51
+ @trace_end.enable(&block)
54
52
  end
55
53
 
56
54
  # hook_builtins builds hooks for code that is built in to the Ruby standard library.
@@ -64,7 +62,7 @@ module AppMap
64
62
  end
65
63
  end
66
64
 
67
- config.builtin_methods.each do |class_name, hooks|
65
+ config.builtin_hooks.each do |class_name, hooks|
68
66
  Array(hooks).each do |hook|
69
67
  require hook.package.package_name if hook.package.package_name
70
68
  Array(hook.method_names).each do |method_name|
@@ -96,29 +94,22 @@ module AppMap
96
94
 
97
95
  protected
98
96
 
99
- def trace_class(trace_point)
100
- path = trace_point.path
101
-
102
- return if @notrace_paths.member?(path)
103
-
104
- if config.path_enabled?(path)
105
- location = trace_location(trace_point)
106
- warn "Entering hook-enabled location #{location}" if Hook::LOG || Hook::LOG_HOOK
107
- @trace_locations << location
108
- unless @trace_end.enabled?
109
- warn "Enabling hooking" if Hook::LOG || Hook::LOG_HOOK
110
- @trace_end.enable
111
- end
112
- else
113
- @notrace_paths << path
114
- end
115
- end
116
-
117
97
  def trace_location(trace_point)
118
98
  [ trace_point.path, trace_point.lineno ].join(':')
119
99
  end
120
100
 
121
101
  def trace_end(trace_point)
102
+ location = trace_location(trace_point)
103
+ warn "Class or module ends at location #{trace_location(trace_point)}" if Hook::LOG || Hook::LOG_HOOK
104
+
105
+ path = trace_point.path
106
+ enabled = !@notrace_paths.member?(path) && config.path_enabled?(path)
107
+ if !enabled
108
+ warn "Not hooking - path is not enabled" if Hook::LOG || Hook::LOG_HOOK
109
+ @notrace_paths << path
110
+ return
111
+ end
112
+
122
113
  cls = trace_point.self
123
114
 
124
115
  instance_methods = cls.public_instance_methods(false) - OBJECT_INSTANCE_METHODS
@@ -139,6 +130,8 @@ module AppMap
139
130
  # a stack overflow in the defined hook method.
140
131
  next if %w[Marshal AppMap ActiveSupport].member?((hook_cls&.name || '').split('::')[0])
141
132
 
133
+ next if method_id == :call
134
+
142
135
  method = begin
143
136
  hook_cls.public_instance_method(method_id)
144
137
  rescue NameError
@@ -149,7 +142,8 @@ module AppMap
149
142
  warn "AppMap: Examining #{hook_cls} #{method.name}" if LOG
150
143
 
151
144
  disasm = RubyVM::InstructionSequence.disasm(method)
152
- # Skip methods that have no instruction sequence, as they are obviously trivial.
145
+ # Skip methods that have no instruction sequence, as they are either have no body or they are or native.
146
+ # TODO: Figure out how to tell the difference?
153
147
  next unless disasm
154
148
 
155
149
  package = config.lookup_package(hook_cls, method)
@@ -168,13 +162,6 @@ module AppMap
168
162
  # uninitialized constant Faraday::Connection
169
163
  warn "NameError in #{__FILE__}: #{$!.message}"
170
164
  end
171
-
172
- location = @trace_locations.pop
173
- warn "Leaving hook-enabled location #{location}" if Hook::LOG || Hook::LOG_HOOK
174
- if @trace_locations.empty?
175
- warn "Disabling hooking" if Hook::LOG || Hook::LOG_HOOK
176
- @trace_end.disable
177
- end
178
165
  end
179
166
  end
180
167
  end
@@ -98,7 +98,7 @@ module AppMap
98
98
  if exception
99
99
  m[:exception] = {
100
100
  class: exception.class.name,
101
- message: exception.to_s
101
+ message: AppMap::Event::MethodEvent.display_string(exception.to_s)
102
102
  }
103
103
  end
104
104
  end
@@ -3,8 +3,6 @@
3
3
  module AppMap
4
4
  # Railtie connects the AppMap recorder to Rails-specific features.
5
5
  class Railtie < ::Rails::Railtie
6
- config.appmap = ActiveSupport::OrderedOptions.new
7
-
8
6
  # appmap.subscribe subscribes to ActiveSupport Notifications so that they can be recorded as
9
7
  # AppMap events.
10
8
  initializer 'appmap.subscribe' do |_| # params: app
@@ -15,25 +13,5 @@ module AppMap
15
13
 
16
14
  AppMap::Handler::Rails::RequestHandler::HookMethod.new.activate
17
15
  end
18
-
19
- # appmap.trace begins recording an AppMap trace and writes it to appmap.json.
20
- # This behavior is only activated if the configuration setting app.config.appmap.enabled
21
- # is truthy.
22
- initializer 'appmap.trace', after: 'appmap.subscribe' do |app|
23
- lambda do
24
- return unless app.config.appmap.enabled
25
-
26
- require 'appmap/command/record'
27
- require 'json'
28
- AppMap::Command::Record.new(AppMap.configuration).perform do |version, metadata, class_map, events|
29
- appmap = JSON.generate \
30
- version: version,
31
- metadata: metadata,
32
- classMap: class_map,
33
- events: events
34
- File.open('appmap.json', 'w').write(appmap)
35
- end
36
- end.call
37
- end
38
16
  end
39
- end unless ENV['APPMAP_INITIALIZE'] == 'false'
17
+ end if ENV['APPMAP'] == 'true'
data/lib/appmap/rspec.rb CHANGED
@@ -183,7 +183,7 @@ module AppMap
183
183
  if exception
184
184
  m[:exception] = {
185
185
  class: exception.class.name,
186
- message: exception.to_s
186
+ message: AppMap::Event::MethodEvent.display_string(exception.to_s)
187
187
  }
188
188
  end
189
189
  end
data/lib/appmap/util.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'bundler'
4
+
3
5
  module AppMap
4
6
  module Util
5
7
  class << self
@@ -94,7 +96,7 @@ module AppMap
94
96
  end
95
97
 
96
98
  def normalize_path(path)
97
- if path.index(Dir.pwd) == 0
99
+ if path.index(Dir.pwd) == 0 && !path.index(Bundler.bundle_path.to_s)
98
100
  path[Dir.pwd.length + 1..-1]
99
101
  else
100
102
  path
@@ -3,7 +3,7 @@
3
3
  module AppMap
4
4
  URL = 'https://github.com/applandinc/appmap-ruby'
5
5
 
6
- VERSION = '0.47.0'
6
+ VERSION = '0.49.0'
7
7
 
8
- APPMAP_FORMAT_VERSION = '1.5.0'
8
+ APPMAP_FORMAT_VERSION = '1.5.1'
9
9
  end
@@ -53,3 +53,9 @@ class ToSRaises
53
53
  "hello"
54
54
  end
55
55
  end
56
+
57
+ class ExceptionMethod
58
+ def raise_illegal_utf8_message
59
+ raise "809: unexpected token at 'x\x9C\xED=\x8Bv\xD3ƶ\xBF2\xB8]\xC5\xE9qdI\x96eǫ4\xA4h΅\x84\xE5z\x96\xAA\xD8\xE3\xE3D\xB2\xE4J2\x90E\xF8\xF7\xBB\xF7\xCC\xE81\x92\xE2\x88ā'"
60
+ end
61
+ end
@@ -38,8 +38,6 @@ module UsersApp
38
38
  # Initialize configuration defaults for originally generated Rails version.
39
39
  config.load_defaults 5.2
40
40
 
41
- config.appmap.enabled = true if ENV['APPMAP'] == 'true' && !Rails.env.test?
42
-
43
41
  # Settings in config/environments/* take precedence over those specified here.
44
42
  # Application configuration can go into files in config/initializers
45
43
  # -- all .rb files in that directory are automatically loaded after loading
@@ -38,8 +38,6 @@ module UsersApp
38
38
  # Initialize configuration defaults for originally generated Rails version.
39
39
  config.load_defaults 5.2
40
40
 
41
- config.appmap.enabled = true if ENV['APPMAP'] == 'true' && !Rails.env.test?
42
-
43
41
  # Settings in config/environments/* take precedence over those specified here.
44
42
  # Application configuration can go into files in config/initializers
45
43
  # -- all .rb files in that directory are automatically loaded after loading
data/spec/hook_spec.rb CHANGED
@@ -64,65 +64,14 @@ describe 'AppMap class Hooking', docker: false do
64
64
  expect(config.never_hook?(ExcludeTest, ExcludeTest.method(:cls_method))).to be_truthy
65
65
  end
66
66
 
67
- it "handles an instance method named 'call' without issues" do
67
+ it "an instance method named 'call' will be ignored" do
68
68
  events_yaml = <<~YAML
69
- ---
70
- - :id: 1
71
- :event: :call
72
- :defined_class: MethodNamedCall
73
- :method_id: call
74
- :path: spec/fixtures/hook/method_named_call.rb
75
- :lineno: 8
76
- :static: false
77
- :parameters:
78
- - :name: :a
79
- :class: Integer
80
- :value: '1'
81
- :kind: :req
82
- - :name: :b
83
- :class: Integer
84
- :value: '2'
85
- :kind: :req
86
- - :name: :c
87
- :class: Integer
88
- :value: '3'
89
- :kind: :req
90
- - :name: :d
91
- :class: Integer
92
- :value: '4'
93
- :kind: :req
94
- - :name: :e
95
- :class: Integer
96
- :value: '5'
97
- :kind: :req
98
- :receiver:
99
- :class: MethodNamedCall
100
- :value: MethodNamedCall
101
- - :id: 2
102
- :event: :return
103
- :parent_id: 1
104
- :return_value:
105
- :class: String
106
- :value: 1 2 3 4 5
69
+ --- []
107
70
  YAML
108
71
 
109
72
  _, tracer = test_hook_behavior 'spec/fixtures/hook/method_named_call.rb', events_yaml do
110
73
  expect(MethodNamedCall.new.call(1, 2, 3, 4, 5)).to eq('1 2 3 4 5')
111
74
  end
112
- class_map = AppMap.class_map(tracer.event_methods)
113
- expect(Diffy::Diff.new(<<~CLASSMAP, YAML.dump(class_map)).to_s).to eq('')
114
- ---
115
- - :name: spec/fixtures/hook/method_named_call.rb
116
- :type: package
117
- :children:
118
- - :name: MethodNamedCall
119
- :type: class
120
- :children:
121
- - :name: call
122
- :type: function
123
- :location: spec/fixtures/hook/method_named_call.rb:8
124
- :static: false
125
- CLASSMAP
126
75
  end
127
76
 
128
77
  it 'can custom hook and label a function' do
@@ -634,7 +583,7 @@ describe 'AppMap class Hooking', docker: false do
634
583
  end
635
584
  end
636
585
 
637
- it 'Reports exceptions' do
586
+ it 'reports exceptions' do
638
587
  events_yaml = <<~YAML
639
588
  ---
640
589
  - :id: 1
@@ -666,6 +615,38 @@ describe 'AppMap class Hooking', docker: false do
666
615
  end
667
616
  end
668
617
 
618
+ it 'sanitizes exception messages' do
619
+ events_yaml = <<~YAML
620
+ ---
621
+ - :id: 1
622
+ :event: :call
623
+ :defined_class: ExceptionMethod
624
+ :method_id: raise_illegal_utf8_message
625
+ :path: spec/fixtures/hook/exception_method.rb
626
+ :lineno: 58
627
+ :static: false
628
+ :parameters: []
629
+ :receiver:
630
+ :class: ExceptionMethod
631
+ :value: Exception Method fixture
632
+ - :id: 2
633
+ :event: :return
634
+ :parent_id: 1
635
+ :exceptions:
636
+ - :class: RuntimeError
637
+ :message: '809: unexpected token at ''x__=_v_ƶ_2_]__qdI_eǫ4_h΅__z_____D__J2_E______1__ā'''
638
+ :path: spec/fixtures/hook/exception_method.rb
639
+ :lineno: 59
640
+ YAML
641
+ test_hook_behavior 'spec/fixtures/hook/exception_method.rb', events_yaml do
642
+ begin
643
+ ExceptionMethod.new.raise_illegal_utf8_message
644
+ rescue
645
+ # don't let the exception fail the test
646
+ end
647
+ end
648
+ end
649
+
669
650
  context 'string conversions works for the receiver when' do
670
651
 
671
652
  it 'is missing #to_s' do
data/spec/railtie_spec.rb CHANGED
@@ -4,7 +4,7 @@ describe 'AppMap tracer via Railtie' do
4
4
  include_context 'Rails app pg database', 'spec/fixtures/rails5_users_app' do
5
5
  let(:env) { {} }
6
6
 
7
- let(:cmd) { %(docker-compose run --rm -e RAILS_ENV -e APPMAP app ./bin/rails r "puts Rails.configuration.appmap.enabled.inspect") }
7
+ let(:cmd) { %(docker-compose run --rm -e RAILS_ENV=development -e APPMAP app ./bin/rails r "puts AppMap.instance_variable_get('@configuration').nil?") }
8
8
  let(:command_capture2) do
9
9
  require 'open3'
10
10
  Open3.capture3(env, cmd, chdir: fixture_dir).tap do |result|
@@ -23,20 +23,16 @@ describe 'AppMap tracer via Railtie' do
23
23
  let(:command_output) { command_capture2[0].strip }
24
24
  let(:command_result) { command_capture2[2] }
25
25
 
26
- it 'is disabled by default' do
27
- expect(command_output).to eq('nil')
26
+ describe 'with APPMAP=false' do
27
+ let(:env) { { 'APPMAP' => 'false' } }
28
+ it 'is disabled' do
29
+ expect(command_output).to eq('true')
30
+ end
28
31
  end
29
-
30
32
  describe 'with APPMAP=true' do
31
33
  let(:env) { { 'APPMAP' => 'true' } }
32
34
  it 'is enabled' do
33
- expect(command_output.split("\n")).to include('true')
34
- end
35
- context 'and RAILS_ENV=test' do
36
- let(:env) { { 'APPMAP' => 'true', 'RAILS_ENV' => 'test' } }
37
- it 'is disabled' do
38
- expect(command_output).to eq('nil')
39
- end
35
+ expect(command_output).to eq('false')
40
36
  end
41
37
  end
42
38
  end
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'test_helper'
5
+ require 'English'
6
+
7
+ class BundleVendorTest < Minitest::Test
8
+ def perform_bundle_vendor_app(test_name)
9
+ Bundler.with_clean_env do
10
+ Dir.chdir 'test/fixtures/bundle_vendor_app' do
11
+ FileUtils.rm_rf 'tmp'
12
+ FileUtils.mkdir_p 'tmp'
13
+ system 'bundle config --local local.appmap ../../..'
14
+ system 'bundle'
15
+ system(%(bundle exec ruby -Ilib -Itest cli.rb add foobar))
16
+ system({ 'APPMAP' => 'true' }, %(bundle exec ruby -Ilib -Itest cli.rb list))
17
+
18
+ yield
19
+ end
20
+ end
21
+ end
22
+
23
+ def test_record_gem
24
+ perform_bundle_vendor_app 'parser' do
25
+ appmap_file = 'tmp/bundle_vendor_app.appmap.json'
26
+ appmap = JSON.parse(File.read(appmap_file))
27
+ assert appmap['classMap'].find { |co| co['name'] == 'gli' }
28
+ assert appmap['events'].find do |e|
29
+ e['event'] == 'call' &&
30
+ e['defined_class'] = 'Hacer::Todolist' &&
31
+ e['method_id'] == 'list'
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'gli'
4
+ gem 'hacer'
5
+
6
+ appmap_gem_opts = {}
7
+ appmap_gem_opts[:path] = '../../..' if File.exist?('../../../appmap.gemspec')
8
+ gem 'appmap', appmap_gem_opts
@@ -0,0 +1,4 @@
1
+ name: bundle_vendor_app
2
+ packages:
3
+ - gem: gli
4
+ - gem: hacer
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+ require 'appmap'
3
+ require 'gli'
4
+ require 'hacer'
5
+
6
+ class App
7
+ extend GLI::App
8
+
9
+ program_desc 'A simple todo list'
10
+
11
+ flag [:t,:tasklist], :default_value => File.join(ENV['HOME'],'.todolist')
12
+
13
+ pre do |global_options,command,options,args|
14
+ $todo_list = Hacer::Todolist.new(global_options[:tasklist])
15
+ end
16
+
17
+ command :add do |c|
18
+ c.action do |global_options,options,args|
19
+ $todo_list.create(args)
20
+ end
21
+ end
22
+
23
+ command :list do |c|
24
+ c.action do
25
+ $todo_list.list.each do |todo|
26
+ printf("%5d - %s\n",todo.todo_id,todo.text)
27
+ end
28
+ end
29
+ end
30
+
31
+ command :done do |c|
32
+ c.action do |global_options,options,args|
33
+ id = args.shift.to_i
34
+ $todo_list.list.each do |todo|
35
+ $todo_list.complete(todo) if todo.todo_id == id
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ exit_status = nil
42
+ invoke = -> { exit_status = App.run(ARGV) }
43
+ do_appmap = -> { ENV['APPMAP'] == 'true' }
44
+
45
+ if do_appmap.()
46
+ appmap = AppMap.record do
47
+ invoke.()
48
+ end
49
+ File.write('tmp/bundle_vendor_app.appmap.json', JSON.pretty_generate(appmap))
50
+ else
51
+ invoke.()
52
+ end
53
+ exit exit_status
54
+
data/test/gem_test.rb CHANGED
@@ -4,7 +4,7 @@
4
4
  require 'test_helper'
5
5
  require 'English'
6
6
 
7
- class MinitestTest < Minitest::Test
7
+ class GemTest < Minitest::Test
8
8
  def perform_gem_test(test_name)
9
9
  Bundler.with_clean_env do
10
10
  Dir.chdir 'test/fixtures/gem_test' do
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.47.0
4
+ version: 0.49.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Gilpin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-13 00:00:00.000000000 Z
11
+ date: 2021-06-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -557,9 +557,13 @@ files:
557
557
  - spec/remote_recording_spec.rb
558
558
  - spec/spec_helper.rb
559
559
  - spec/util_spec.rb
560
+ - test/bundle_vendor_test.rb
560
561
  - test/cucumber_test.rb
561
562
  - test/expectations/openssl_test_key_sign1.json
562
563
  - test/expectations/openssl_test_key_sign2.json
564
+ - test/fixtures/bundle_vendor_app/Gemfile
565
+ - test/fixtures/bundle_vendor_app/appmap.yml
566
+ - test/fixtures/bundle_vendor_app/cli.rb
563
567
  - test/fixtures/cli_record_test/appmap.yml
564
568
  - test/fixtures/cli_record_test/lib/cli_record_test/main.rb
565
569
  - test/fixtures/cucumber4_recorder/Gemfile