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 +4 -4
- data/.dockerignore +0 -1
- data/CHANGELOG.md +37 -0
- data/README.md +3 -348
- data/lib/appmap.rb +1 -1
- data/lib/appmap/config.rb +101 -41
- data/lib/appmap/event.rb +1 -1
- data/lib/appmap/handler/rails/template.rb +10 -4
- data/lib/appmap/hook.rb +18 -31
- data/lib/appmap/minitest.rb +1 -1
- data/lib/appmap/railtie.rb +1 -23
- data/lib/appmap/rspec.rb +1 -1
- data/lib/appmap/util.rb +3 -1
- data/lib/appmap/version.rb +2 -2
- data/spec/fixtures/hook/exception_method.rb +6 -0
- data/spec/fixtures/rails5_users_app/config/application.rb +0 -2
- data/spec/fixtures/rails6_users_app/config/application.rb +0 -2
- data/spec/hook_spec.rb +35 -54
- data/spec/railtie_spec.rb +7 -11
- data/test/bundle_vendor_test.rb +35 -0
- data/test/fixtures/bundle_vendor_app/Gemfile +8 -0
- data/test/fixtures/bundle_vendor_app/appmap.yml +4 -0
- data/test/fixtures/bundle_vendor_app/cli.rb +54 -0
- data/test/gem_test.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '02814a0f9d0927d19c2d341a22d8f8ec66b83bc022833a09a7777ce81ec4edfc'
|
4
|
+
data.tar.gz: 11b2786d88e360fc78e046675ae799e7106625dd02b4a3070c3835bfe8feea05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c852ae464d52f9f29ef9222fa1627f99b4f5baf1907b1cd54cbfb969ff2323cb19e0d5b53bad1356518ec21a07243ea71237ad831a62ffe825a2601cca1a5cf7
|
7
|
+
data.tar.gz: 49c5ba0d0c20d8bee6e4c768544ab6abe0f0f0d71a1a59bc04fcf867e93bc743eda4cc94f7505c0a1194c07e95e2b8e7f875a42b16aa3da2a21068a61ba42a10
|
data/.dockerignore
CHANGED
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
|
-
|
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
|
-
|
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
|
-
|
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
data/lib/appmap/config.rb
CHANGED
@@ -85,18 +85,7 @@ module AppMap
|
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
|
-
|
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
|
-
|
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
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
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
|
-
|
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' =>
|
165
|
-
|
166
|
-
|
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, :
|
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
|
-
@
|
233
|
+
@builtin_hooks = BUILTIN_HOOKS
|
177
234
|
@functions = functions
|
178
|
-
|
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
|
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
|
-
|
109
|
+
path_obj = Array(return_value).first
|
110
|
+
|
111
|
+
warn "Resolver return: #{path_obj}" if LOG
|
112
112
|
|
113
|
-
if
|
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
|
-
@
|
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.
|
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
|
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
|
data/lib/appmap/minitest.rb
CHANGED
data/lib/appmap/railtie.rb
CHANGED
@@ -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
|
17
|
+
end if ENV['APPMAP'] == 'true'
|
data/lib/appmap/rspec.rb
CHANGED
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
|
data/lib/appmap/version.rb
CHANGED
@@ -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 "
|
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 '
|
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
|
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
|
-
|
27
|
-
|
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
|
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,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
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: appmap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.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-
|
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
|