appmap 0.18.1 → 0.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +77 -39
- data/lib/appmap/rspec.rb +47 -14
- data/lib/appmap/trace/tracer.rb +15 -6
- data/lib/appmap/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dbd73ea1339788825987a09e80097ad54fcaf6297ae5a7bbe01aa39b5462a8bb
|
4
|
+
data.tar.gz: 6338cbb3203543ce4db169fef9cfcf31acfd6ea44da94220bca5c00638368520
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d0b9d851b266a230a40ac85e9991d183919c67b1c09e155009ffb3cd2de660e40521e6253534ae6a3e06867238810d6f5def1c9b10f094f81ed2498e79ee4ea
|
7
|
+
data.tar.gz: 4a6f75650e36876086782f778eb1fbb90f7d15a9e35eb28f245ce942285b3cfd95c76c16e5d01a5c90867455668609e974a41dfd829a6e7a91487833ebee49c9
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
# v0.19.0
|
2
|
+
|
3
|
+
* **RSpec** feature and feature group names can be inferred from example group and example names.
|
4
|
+
* Stop using `ActiveSupport::Inflector.transliterate`, since it can cause exceptions.
|
5
|
+
* Handle StandardError which occurs while calling `#inspect` of an object.
|
6
|
+
|
1
7
|
# v0.18.1
|
2
8
|
|
3
9
|
* Now tested with Rails 4, 5, and 6.
|
data/README.md
CHANGED
@@ -1,35 +1,54 @@
|
|
1
1
|
- [About](#about)
|
2
2
|
- [Installation](#installation)
|
3
3
|
- [Configuration](#configuration)
|
4
|
-
- [`packages`](#packages)
|
5
4
|
- [Running](#running)
|
6
5
|
- [RSpec](#rspec)
|
7
|
-
- [
|
8
|
-
- [Uploading](#uploading)
|
6
|
+
- [Remote recording](#remote-recording)
|
7
|
+
- [Uploading AppMaps](#uploading-appmaps)
|
9
8
|
- [Build status](#build-status)
|
10
9
|
|
11
10
|
# About
|
12
11
|
|
13
|
-
`appmap-ruby` is a Ruby
|
14
|
-
|
12
|
+
`appmap-ruby` is a Ruby Gem for recording and uploading
|
13
|
+
[AppMaps](https://github.com/applandinc/appmap) of your code.
|
15
14
|
AppMap is a data format which records code structure (modules, classes, and methods), code execution events
|
16
|
-
(function calls and returns), and code metadata (repo name, repo URL, commit
|
15
|
+
(function calls and returns), and code metadata (repo name, repo URL, commit
|
16
|
+
SHA, etc). It's more granular than a performance profile, but it's less
|
17
|
+
granular than a full debug trace. It's designed to be optimal for understanding the design intent and behavior of code.
|
18
|
+
|
19
|
+
There are several ways to record AppMaps of your Ruby program using the `appmap` gem:
|
17
20
|
|
18
|
-
|
19
|
-
|
21
|
+
* Run your RSpec tests. An AppMap will be generated for each one.
|
22
|
+
* Run your application server with AppMap remote recording enabled, and use the AppMap
|
23
|
+
browser extension to start, stop, and upload recordings.
|
20
24
|
|
21
|
-
|
22
|
-
|
25
|
+
When you record AppMaps on the command line (for example, by running RSpec tests), you use the `appmap upload` command to
|
26
|
+
upload them to the AppLand website. On the AppLand website, you'll be able to
|
27
|
+
visualize the design of your code and share links with collaborators.
|
23
28
|
|
24
29
|
# Installation
|
25
30
|
|
26
|
-
Add `gem 'appmap'` to your Gemfile just as you would any other dependency.
|
31
|
+
Add `gem 'appmap'` to your Gemfile just as you would any other dependency.
|
32
|
+
|
33
|
+
**Global installation**
|
34
|
+
|
35
|
+
```
|
36
|
+
gem 'appmap'
|
37
|
+
```
|
38
|
+
|
39
|
+
**Install in test, development groups**
|
40
|
+
|
41
|
+
```
|
42
|
+
group :development, :test do
|
43
|
+
gem 'appmap'
|
44
|
+
end
|
45
|
+
```
|
27
46
|
|
28
47
|
Then install with `bundle`.
|
29
48
|
|
30
49
|
# Configuration
|
31
50
|
|
32
|
-
When you run
|
51
|
+
When you run your program, the `appmap` gem reads configuration settings from `appmap.yml`. Here's a sample configuration
|
33
52
|
file for a typical Rails project:
|
34
53
|
|
35
54
|
```yaml
|
@@ -44,7 +63,7 @@ packages:
|
|
44
63
|
* **files** A list of individual files which should be instrumented. This is only used for files which are
|
45
64
|
not part of the `packages` list.
|
46
65
|
|
47
|
-
|
66
|
+
**packages**
|
48
67
|
|
49
68
|
Each entry in the `packages` list is a YAML object which has the following keys:
|
50
69
|
|
@@ -61,71 +80,90 @@ Each entry in the `packages` list is a YAML object which has the following keys:
|
|
61
80
|
|
62
81
|
To instrument RSpec tests, follow these steps:
|
63
82
|
|
64
|
-
1) Include the `appmap` gem in your Gemfile
|
65
|
-
2) Require `appmap/rspec` in your `spec_helper.rb
|
66
|
-
3) Add `appmap: true` to the tests you want to instrument
|
67
|
-
4)
|
83
|
+
1) Include the `appmap` gem in your Gemfile.
|
84
|
+
2) Require `appmap/rspec` in your `spec_helper.rb`.
|
85
|
+
3) Add `appmap: true` to the tests you want to instrument.
|
86
|
+
4) Export the environment variable `APPMAP=true`.
|
87
|
+
5) *Optional* Add `feature: '<feature name>'` and `feature_group: '<feature group name>'` annotations to your
|
68
88
|
examples.
|
69
|
-
5) Export the environment variable `APPMAP=true`
|
70
89
|
|
71
90
|
Here's an example of an appmap-enabled RSpec test:
|
72
91
|
|
73
92
|
```ruby
|
74
|
-
describe Hello,
|
75
|
-
|
76
|
-
|
93
|
+
describe Hello, appmap: true do
|
94
|
+
describe 'says hello' do
|
95
|
+
it 'when prompted' do
|
96
|
+
expect(Hello.new.say_hello).to eq('Hello!')
|
97
|
+
end
|
77
98
|
end
|
78
99
|
end
|
79
100
|
```
|
80
101
|
|
81
|
-
|
102
|
+
Run the tests like this:
|
82
103
|
|
83
104
|
```sh-session
|
84
|
-
$ APPMAP=true bundle exec rspec
|
105
|
+
$ APPMAP=true bundle exec rspec -t appmap
|
85
106
|
```
|
86
107
|
|
87
108
|
Each RSpec test will output a data file into the directory `tmp/appmap/rspec`. For example:
|
88
109
|
|
89
110
|
```
|
90
111
|
$ find tmp/appmap/rspec
|
91
|
-
|
112
|
+
Hello_says_hello_when_prompted.appmap.json
|
92
113
|
```
|
93
114
|
|
94
115
|
If you include the `feature` and `feature_group` metadata, these attributes will be exported to the AppMap file in the
|
95
116
|
`metadata` section. It will look something like this:
|
96
117
|
|
97
118
|
```json
|
98
|
-
|
119
|
+
{
|
120
|
+
...
|
121
|
+
"metadata": {
|
122
|
+
"name": "Hello app says hello when prompted",
|
123
|
+
"feature": "Hello app says hello",
|
124
|
+
"feature_group": "Hello"
|
125
|
+
},
|
126
|
+
...
|
127
|
+
}
|
99
128
|
```
|
100
129
|
|
101
|
-
##
|
130
|
+
## Remote recording
|
102
131
|
|
103
|
-
To
|
132
|
+
To manually record ad-hoc AppMaps of your Ruby app, use AppMap remote recording.
|
104
133
|
|
105
|
-
1
|
134
|
+
1. Add the AppMap remote recording middleware. For example, in `config/initializers/appmap_remote_recording.rb`:
|
106
135
|
|
107
136
|
```ruby
|
108
|
-
|
137
|
+
require 'appmap/middleware/remote_recording'
|
138
|
+
|
139
|
+
unless Rails.env.test?
|
140
|
+
Rails.application.config.middleware.insert_after \
|
141
|
+
Rails::Rack::Logger,
|
142
|
+
AppMap::Middleware::RemoteRecording
|
143
|
+
end
|
109
144
|
```
|
110
145
|
|
111
|
-
2
|
146
|
+
2. Start your Rails application server. For example:
|
112
147
|
|
113
148
|
```sh-session
|
114
|
-
$
|
149
|
+
$ bundle exec rails server
|
115
150
|
```
|
116
151
|
|
117
|
-
|
118
|
-
|
152
|
+
3. Open the AppApp browser extension and push `Start`.
|
153
|
+
|
154
|
+
4. Use your app. For example, perform a login flow, or run through a manual UI test.
|
155
|
+
|
156
|
+
5. Open the AppApp browser extension and push `Stop`. The recording will be transferred to the AppLand website and opened in your browser.
|
119
157
|
|
120
|
-
# Uploading
|
158
|
+
# Uploading AppMaps
|
121
159
|
|
122
|
-
To upload an AppMap file to
|
160
|
+
To upload an AppMap file to AppLand, run the `appmap upload` command. For example:
|
123
161
|
|
124
162
|
```sh-session
|
125
|
-
$ appmap upload tmp/appmap/rspec/
|
126
|
-
|
127
|
-
|
128
|
-
|
163
|
+
$ appmap upload tmp/appmap/rspec/Hello_app_says_hello_when_prompted.appmap.json
|
164
|
+
Uploading "tmp/appmap/rspec/Hello_app_says_hello_when_prompted.appmap.json"
|
165
|
+
Scenario Id: 4da4f267-bdea-48e8-bf67-f39463844230
|
166
|
+
Batch Id: a116f1df-ee57-4bde-8eef-851af0f3d7bc
|
129
167
|
```
|
130
168
|
|
131
169
|
# Build status
|
data/lib/appmap/rspec.rb
CHANGED
@@ -29,14 +29,14 @@ module AppMap
|
|
29
29
|
FileUtils.mkdir_p APPMAP_OUTPUT_DIR
|
30
30
|
end
|
31
31
|
|
32
|
-
|
33
|
-
def save(example_name, events, feature_name: nil, feature_group_name: nil)
|
32
|
+
def save(example_name, events, feature_name: nil, feature_group_name: nil, labels: nil)
|
34
33
|
require 'appmap/command/record'
|
35
34
|
metadata = AppMap::Command::Record.detect_metadata.tap do |m|
|
36
35
|
m[:name] = example_name
|
37
36
|
m[:app] = @config.name
|
38
37
|
m[:feature] = feature_name if feature_name
|
39
38
|
m[:feature_group] = feature_group_name if feature_group_name
|
39
|
+
m[:labels] = labels if labels
|
40
40
|
m[:frameworks] ||= []
|
41
41
|
m[:frameworks] << {
|
42
42
|
name: 'rspec',
|
@@ -58,7 +58,7 @@ module AppMap
|
|
58
58
|
# https://github.com/rails/rails/blob/v5.2.4/activesupport/lib/active_support/inflector/transliterate.rb#L92
|
59
59
|
def sanitize_filename(fname, separator: '_')
|
60
60
|
# Replace accented chars with their ASCII equivalents.
|
61
|
-
fname =
|
61
|
+
fname = fname.encode('utf-8', invalid: :replace, undef: :replace, replace: '_')
|
62
62
|
|
63
63
|
# Turn unwanted chars into the separator.
|
64
64
|
fname.gsub!(/[^a-z0-9\-_]+/i, separator)
|
@@ -83,6 +83,17 @@ module AppMap
|
|
83
83
|
annotations[:feature]
|
84
84
|
end
|
85
85
|
|
86
|
+
def labels
|
87
|
+
labels = metadata[:appmap]
|
88
|
+
if labels.is_a?(Array)
|
89
|
+
labels
|
90
|
+
elsif labels.is_a?(String) || labels.is_a?(Symbol)
|
91
|
+
[ labels ]
|
92
|
+
else
|
93
|
+
[]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
86
97
|
def feature_group
|
87
98
|
return nil unless annotations
|
88
99
|
|
@@ -119,6 +130,10 @@ module AppMap
|
|
119
130
|
|
120
131
|
alias_method :example_obj, :example
|
121
132
|
|
133
|
+
def description?
|
134
|
+
true
|
135
|
+
end
|
136
|
+
|
122
137
|
def description
|
123
138
|
example.description
|
124
139
|
end
|
@@ -255,28 +270,46 @@ module AppMap
|
|
255
270
|
|
256
271
|
example = examples[loc]
|
257
272
|
description = []
|
258
|
-
scope = ScopeExample.new(example)
|
273
|
+
leaf = scope = ScopeExample.new(example)
|
259
274
|
feature_group = feature = nil
|
275
|
+
labels = scope.labels.map(&:to_s).map(&:strip).reject(&:blank?).map(&:downcase).uniq
|
276
|
+
|
260
277
|
while scope
|
261
278
|
description << scope.description
|
262
279
|
feature ||= scope.feature
|
263
280
|
feature_group ||= scope.feature_group
|
264
281
|
scope = scope.parent
|
265
282
|
end
|
266
|
-
description.reject!
|
283
|
+
description.reject!(&:nil?).reject(&:blank?)
|
284
|
+
default_description = description.last
|
267
285
|
description.reverse!
|
268
286
|
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
287
|
+
normalize = lambda do |desc|
|
288
|
+
desc.gsub('it should behave like', '')
|
289
|
+
.gsub(/Controller$/, '')
|
290
|
+
.gsub(/\s+/, ' ')
|
291
|
+
.strip
|
292
|
+
end
|
293
|
+
|
294
|
+
full_description = normalize.call(description.join(' '))
|
295
|
+
|
296
|
+
compute_feature_name = lambda do
|
297
|
+
return 'unknown' if description.empty?
|
298
|
+
|
299
|
+
feature_description = description.dup
|
300
|
+
num_tokens = [2, feature_description.length - 1].min
|
301
|
+
feature_description[0...num_tokens].map(&:strip).join(' ')
|
274
302
|
end
|
275
|
-
full_description = description.join(' ')
|
276
303
|
|
277
|
-
|
278
|
-
|
279
|
-
|
304
|
+
feature_group ||= normalize.call(default_description).underscore.gsub('/', '_').humanize
|
305
|
+
feature_name = feature || compute_feature_name.call if feature_group
|
306
|
+
feature_name = normalize.call(feature_name) if feature_name
|
307
|
+
|
308
|
+
recorder.save full_description,
|
309
|
+
events,
|
310
|
+
feature_name: feature_name,
|
311
|
+
feature_group_name: feature_group,
|
312
|
+
labels: labels.blank? ? nil : labels
|
280
313
|
end
|
281
314
|
end
|
282
315
|
end
|
data/lib/appmap/trace/tracer.rb
CHANGED
@@ -87,15 +87,24 @@ module AppMap
|
|
87
87
|
def display_string(value)
|
88
88
|
return nil unless value
|
89
89
|
|
90
|
-
|
91
|
-
|
92
|
-
rescue NoMethodError
|
93
|
-
value_string = value.inspect
|
94
|
-
rescue StandardError
|
95
|
-
warn $!.message
|
90
|
+
last_resort_string = lambda do
|
91
|
+
warn "AppMap encountered an error inspecting a #{value.class.name}: #{$!.message}"
|
96
92
|
'*Error inspecting variable*'
|
97
93
|
end
|
98
94
|
|
95
|
+
value_string = \
|
96
|
+
begin
|
97
|
+
value.to_s
|
98
|
+
rescue NoMethodError
|
99
|
+
begin
|
100
|
+
value.inspect
|
101
|
+
rescue StandardError
|
102
|
+
last_resort_string.call
|
103
|
+
end
|
104
|
+
rescue StandardError
|
105
|
+
last_resort_string.call
|
106
|
+
end
|
107
|
+
|
99
108
|
value_string[0...LIMIT].encode('utf-8', invalid: :replace, undef: :replace, replace: '_')
|
100
109
|
end
|
101
110
|
end
|
data/lib/appmap/version.rb
CHANGED