appmap 0.41.1 → 0.44.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +17 -2
- data/CHANGELOG.md +31 -0
- data/README.md +54 -22
- data/appmap.gemspec +0 -2
- data/lib/appmap.rb +3 -2
- data/lib/appmap/class_map.rb +7 -10
- data/lib/appmap/config.rb +94 -34
- data/lib/appmap/cucumber.rb +1 -1
- data/lib/appmap/event.rb +18 -0
- data/lib/appmap/hook.rb +42 -22
- data/lib/appmap/hook/method.rb +1 -1
- data/lib/appmap/minitest.rb +35 -30
- data/lib/appmap/rails/request_handler.rb +41 -10
- data/lib/appmap/record.rb +1 -1
- data/lib/appmap/rspec.rb +32 -96
- data/lib/appmap/util.rb +16 -0
- data/lib/appmap/version.rb +1 -1
- data/patch +1447 -0
- data/spec/abstract_controller_base_spec.rb +69 -26
- data/spec/class_map_spec.rb +3 -11
- data/spec/config_spec.rb +31 -1
- data/spec/fixtures/hook/custom_instance_method.rb +11 -0
- data/spec/fixtures/hook/method_named_call.rb +11 -0
- data/spec/fixtures/rails5_users_app/Gemfile +7 -3
- data/spec/fixtures/rails5_users_app/app/controllers/api/users_controller.rb +2 -0
- data/spec/fixtures/rails5_users_app/app/controllers/users_controller.rb +9 -1
- data/spec/fixtures/rails5_users_app/config/application.rb +2 -0
- data/spec/fixtures/rails5_users_app/create_app +8 -2
- data/spec/fixtures/rails5_users_app/docker-compose.yml +3 -0
- data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_api_spec.rb +16 -3
- data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_spec.rb +2 -2
- data/spec/fixtures/rails5_users_app/spec/models/user_spec.rb +2 -12
- data/spec/fixtures/rails5_users_app/spec/rails_helper.rb +3 -9
- data/spec/fixtures/rails6_users_app/Gemfile +5 -4
- data/spec/fixtures/rails6_users_app/app/controllers/api/users_controller.rb +1 -0
- data/spec/fixtures/rails6_users_app/app/controllers/users_controller.rb +9 -1
- data/spec/fixtures/rails6_users_app/config/application.rb +2 -0
- data/spec/fixtures/rails6_users_app/create_app +8 -2
- data/spec/fixtures/rails6_users_app/docker-compose.yml +3 -0
- data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_api_spec.rb +16 -3
- data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_spec.rb +2 -2
- data/spec/fixtures/rails6_users_app/spec/models/user_spec.rb +2 -12
- data/spec/fixtures/rails6_users_app/spec/rails_helper.rb +3 -9
- data/spec/hook_spec.rb +134 -10
- data/spec/record_sql_rails_pg_spec.rb +1 -1
- data/spec/spec_helper.rb +6 -0
- data/test/expectations/openssl_test_key_sign1.json +2 -4
- data/test/fixtures/rspec_recorder/spec/decorated_hello_spec.rb +3 -3
- data/test/fixtures/rspec_recorder/spec/plain_hello_spec.rb +1 -1
- data/test/gem_test.rb +1 -1
- data/test/minitest_test.rb +1 -2
- data/test/rspec_test.rb +1 -20
- metadata +7 -8
- data/exe/appmap +0 -154
- data/spec/rspec_feature_metadata_spec.rb +0 -31
- data/test/cli_test.rb +0 -116
data/lib/appmap/util.rb
CHANGED
@@ -71,6 +71,22 @@ module AppMap
|
|
71
71
|
|
72
72
|
event
|
73
73
|
end
|
74
|
+
|
75
|
+
# Atomically writes AppMap data to +filename+.
|
76
|
+
def write_appmap(filename, appmap)
|
77
|
+
require 'fileutils'
|
78
|
+
require 'tmpdir'
|
79
|
+
|
80
|
+
# This is what Ruby Tempfile does; but we don't want the file to be unlinked.
|
81
|
+
mode = File::RDWR | File::CREAT | File::EXCL
|
82
|
+
::Dir::Tmpname.create([ 'appmap_', '.json' ]) do |tmpname|
|
83
|
+
tempfile = File.open(tmpname, mode)
|
84
|
+
tempfile.write(appmap)
|
85
|
+
tempfile.close
|
86
|
+
# Atomically move the tempfile into place.
|
87
|
+
FileUtils.mv tempfile.path, filename
|
88
|
+
end
|
89
|
+
end
|
74
90
|
end
|
75
91
|
end
|
76
92
|
end
|
data/lib/appmap/version.rb
CHANGED
data/patch
ADDED
@@ -0,0 +1,1447 @@
|
|
1
|
+
diff --git a/.travis.yml b/.travis.yml
|
2
|
+
index ab6ccca..f165339 100644
|
3
|
+
--- a/.travis.yml
|
4
|
+
+++ b/.travis.yml
|
5
|
+
@@ -13,11 +13,26 @@ services:
|
6
|
+
# necessary.
|
7
|
+
before_script:
|
8
|
+
- unset RAILS_ENV
|
9
|
+
-
|
10
|
+
+
|
11
|
+
+cache:
|
12
|
+
+ bundler: true
|
13
|
+
+ directories:
|
14
|
+
+ - $HOME/docker
|
15
|
+
+
|
16
|
+
+# https://stackoverflow.com/a/41975912
|
17
|
+
+before_cache:
|
18
|
+
+ # Save tagged docker images
|
19
|
+
+ - >
|
20
|
+
+ mkdir -p $HOME/docker && docker images -a --filter='dangling=false' --format '{{.Repository}}:{{.Tag}} {{.ID}}'
|
21
|
+
+ | xargs -n 2 -t sh -c 'test -e $HOME/docker/$1.tar.gz || docker save $0 | gzip -2 > $HOME/docker/$1.tar.gz'
|
22
|
+
+
|
23
|
+
+before_install:
|
24
|
+
+ # Load cached docker images
|
25
|
+
+ - if [[ -d $HOME/docker ]]; then ls $HOME/docker/*.tar.gz | xargs -I {file} sh -c "zcat {file} | docker load"; fi
|
26
|
+
+
|
27
|
+
jobs:
|
28
|
+
include:
|
29
|
+
- stage: test
|
30
|
+
script:
|
31
|
+
- mkdir tmp
|
32
|
+
- bundle exec rake test
|
33
|
+
-
|
34
|
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
|
35
|
+
index e3ea210..4a25fba 100644
|
36
|
+
--- a/CHANGELOG.md
|
37
|
+
+++ b/CHANGELOG.md
|
38
|
+
@@ -1,3 +1,12 @@
|
39
|
+
+# v0.44.0
|
40
|
+
+
|
41
|
+
+* Support recording and labeling of indivudal functions via `functions:` section in *appmap.yml*.
|
42
|
+
+* Remove deprecated `exe/appmap`.
|
43
|
+
+* Add `test_status` and `exception` fields to AppMap metadata.
|
44
|
+
+* Write AppMap file atomically, by writing to a temp file first and then moving it into place.
|
45
|
+
+* Remove printing of `Inventory.json` file.
|
46
|
+
+* Remove source code from `classMap`.
|
47
|
+
+
|
48
|
+
# v0.43.0
|
49
|
+
|
50
|
+
* Record `name` and `class` of each entry in Hash-like parameters, messages, and return values.
|
51
|
+
diff --git a/README.md b/README.md
|
52
|
+
index b9e6eca..e5674d9 100644
|
53
|
+
--- a/README.md
|
54
|
+
+++ b/README.md
|
55
|
+
@@ -110,6 +110,9 @@ name: my_project
|
56
|
+
packages:
|
57
|
+
- path: app/controllers
|
58
|
+
- path: app/models
|
59
|
+
+ # Exclude sub-paths within the package path
|
60
|
+
+ exclude:
|
61
|
+
+ - concerns/accessor
|
62
|
+
- path: app/jobs
|
63
|
+
- path: app/helpers
|
64
|
+
# Include the gems that you want to see in the dependency maps.
|
65
|
+
@@ -118,15 +121,22 @@ packages:
|
66
|
+
- gem: devise
|
67
|
+
- gem: aws-sdk
|
68
|
+
- gem: will_paginate
|
69
|
+
+# Global exclusion of a class name
|
70
|
+
exclude:
|
71
|
+
- MyClass
|
72
|
+
- MyClass#my_instance_method
|
73
|
+
- MyClass.my_class_method
|
74
|
+
+functions:
|
75
|
+
+- packages: myapp
|
76
|
+
+ class: ControllerHelper
|
77
|
+
+ function: logged_in_user
|
78
|
+
+ labels: [ authentication ]
|
79
|
+
```
|
80
|
+
|
81
|
+
* **name** Provides the project name (required)
|
82
|
+
* **packages** A list of source code directories which should be recorded.
|
83
|
+
* **exclude** A list of classes and/or methods to definitively exclude from recording.
|
84
|
+
+* **functions** A list of specific functions, scoped by package and class, to record.
|
85
|
+
|
86
|
+
**packages**
|
87
|
+
|
88
|
+
@@ -145,6 +155,11 @@ Each entry in the `packages` list is a YAML object which has the following keys:
|
89
|
+
|
90
|
+
Optional list of fully qualified class and method names. Separate class and method names with period (`.`) for class methods and hash (`#`) for instance methods.
|
91
|
+
|
92
|
+
+**functions**
|
93
|
+
+
|
94
|
+
+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
|
95
|
+
+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).
|
96
|
+
+For functions which are part of the application code, use `@label` or `@labels` in code comments to apply labels.
|
97
|
+
|
98
|
+
# Labels
|
99
|
+
|
100
|
+
@@ -344,7 +359,7 @@ Each interactive diagram links directly to the source code, and the information
|
101
|
+
# AppMap Swagger
|
102
|
+
|
103
|
+
[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.
|
104
|
+
-
|
105
|
+
+n
|
106
|
+
# Uploading AppMaps
|
107
|
+
|
108
|
+
[https://app.land](https://app.land) can be used to store, analyze, and share AppMaps.
|
109
|
+
diff --git a/appmap.gemspec b/appmap.gemspec
|
110
|
+
index 7a01cb9..8b2af31 100644
|
111
|
+
--- a/appmap.gemspec
|
112
|
+
+++ b/appmap.gemspec
|
113
|
+
@@ -20,8 +20,6 @@ Gem::Specification.new do |spec|
|
114
|
+
")
|
115
|
+
spec.extensions << "ext/appmap/extconf.rb"
|
116
|
+
|
117
|
+
- spec.bindir = 'exe'
|
118
|
+
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
119
|
+
spec.require_paths = ['lib']
|
120
|
+
|
121
|
+
spec.add_dependency 'activesupport'
|
122
|
+
diff --git a/exe/appmap b/exe/appmap
|
123
|
+
deleted file mode 100755
|
124
|
+
index c6bd228..0000000
|
125
|
+
--- a/exe/appmap
|
126
|
+
+++ /dev/null
|
127
|
+
@@ -1,154 +0,0 @@
|
128
|
+
-#!/usr/bin/env ruby
|
129
|
+
-# frozen_string_literal: true
|
130
|
+
-
|
131
|
+
-require 'gli'
|
132
|
+
-
|
133
|
+
-ENV['APPMAP_INITIALIZE'] = 'false'
|
134
|
+
-
|
135
|
+
-require 'appmap'
|
136
|
+
-require 'appmap/version'
|
137
|
+
-
|
138
|
+
-# AppMap CLI.
|
139
|
+
-module AppMap
|
140
|
+
- class App
|
141
|
+
- extend GLI::App
|
142
|
+
-
|
143
|
+
- program_desc 'AppMap client'
|
144
|
+
-
|
145
|
+
- version AppMap::VERSION
|
146
|
+
-
|
147
|
+
- subcommand_option_handling :normal
|
148
|
+
- arguments :strict
|
149
|
+
- preserve_argv true
|
150
|
+
-
|
151
|
+
- class << self
|
152
|
+
- protected
|
153
|
+
-
|
154
|
+
- def default_appmap_file
|
155
|
+
- ENV['APPMAP_FILE'] || 'appmap.json'
|
156
|
+
- end
|
157
|
+
-
|
158
|
+
- def output_file_flag(c, default_value: nil)
|
159
|
+
- c.desc 'Name of the output file'
|
160
|
+
- c.long_desc <<~DESC
|
161
|
+
- Use a single dash '-' for stdout.
|
162
|
+
- DESC
|
163
|
+
- c.default_value default_value if default_value
|
164
|
+
- c.arg_name 'filename'
|
165
|
+
- c.flag %i[o output]
|
166
|
+
- end
|
167
|
+
- end
|
168
|
+
-
|
169
|
+
- desc 'AppMap configuration file name'
|
170
|
+
- default_value ENV['APPMAP_CONFIG'] || 'appmap.yml'
|
171
|
+
- arg_name 'filename'
|
172
|
+
- flag %i[c config]
|
173
|
+
-
|
174
|
+
- desc 'Record the execution of a program and generate an AppMap.'
|
175
|
+
- arg_name 'program'
|
176
|
+
- command :record do |c|
|
177
|
+
- output_file_flag(c, default_value: default_appmap_file)
|
178
|
+
-
|
179
|
+
- c.action do |_, _, args|
|
180
|
+
- # My subcommand name
|
181
|
+
- ARGV.shift
|
182
|
+
-
|
183
|
+
- # Consume the :output option, if provided
|
184
|
+
- if %w[-o --output].find { |arg_name| ARGV[0] == arg_name.to_s }
|
185
|
+
- ARGV.shift
|
186
|
+
- ARGV.shift
|
187
|
+
- end
|
188
|
+
-
|
189
|
+
- # Name of the program to execute. GLI will ensure that it's present.
|
190
|
+
- program = args.shift or help_now!("'program' argument is required")
|
191
|
+
-
|
192
|
+
- # Also pop the program name from ARGV, because the command will use raw ARGV
|
193
|
+
- # to load the extra arguments into this Ruby process.
|
194
|
+
- ARGV.shift
|
195
|
+
-
|
196
|
+
- require 'appmap/command/record'
|
197
|
+
- AppMap::Command::Record.new(@config, program).perform do |version, metadata, class_map, events|
|
198
|
+
- @output_file.write JSON.generate(version: version,
|
199
|
+
- metadata: metadata,
|
200
|
+
- classMap: class_map,
|
201
|
+
- events: events)
|
202
|
+
- end
|
203
|
+
- end
|
204
|
+
- end
|
205
|
+
-
|
206
|
+
- desc 'Calculate and print statistics of scenario files.'
|
207
|
+
- arg_name 'filename'
|
208
|
+
- command :stats do |c|
|
209
|
+
- output_file_flag(c, default_value: '-')
|
210
|
+
-
|
211
|
+
- c.desc 'Display format for the result (text | json)'
|
212
|
+
- c.default_value 'text'
|
213
|
+
- c.flag %i[f format]
|
214
|
+
-
|
215
|
+
- c.desc 'Maximum number of lines to display for each stat'
|
216
|
+
- c.flag %i[l limit]
|
217
|
+
-
|
218
|
+
- c.action do |_, options, args|
|
219
|
+
- require 'appmap/command/stats'
|
220
|
+
-
|
221
|
+
- limit = options[:limit].to_i if options[:limit]
|
222
|
+
-
|
223
|
+
- # Name of the file to analyze. GLI will ensure that it's present.
|
224
|
+
- filenames = args
|
225
|
+
- help_now!("'filename' argument is required") if filenames.empty?
|
226
|
+
-
|
227
|
+
- require 'appmap/algorithm/stats'
|
228
|
+
- result = filenames.inject(::AppMap::Algorithm::Stats::Result.new([], [])) do |stats_result, filename|
|
229
|
+
- appmap = begin
|
230
|
+
- JSON.parse(File.read(filename))
|
231
|
+
- rescue JSON::ParserError
|
232
|
+
- STDERR.puts "#{filename} is not valid JSON : #{$!}"
|
233
|
+
- nil
|
234
|
+
- end
|
235
|
+
- stats_result.tap do
|
236
|
+
- if appmap
|
237
|
+
- limit = options[:limit].to_i if options[:limit]
|
238
|
+
- stats_for_file = AppMap::Command::Stats.new(appmap).perform(limit: limit)
|
239
|
+
- stats_result.merge!(stats_for_file)
|
240
|
+
- end
|
241
|
+
- end
|
242
|
+
- end
|
243
|
+
-
|
244
|
+
- result.sort!
|
245
|
+
- result.limit!(limit) if limit
|
246
|
+
-
|
247
|
+
- display = case options[:format]
|
248
|
+
- when 'json'
|
249
|
+
- JSON.pretty_generate(result.as_json)
|
250
|
+
- else
|
251
|
+
- result.as_text
|
252
|
+
- end
|
253
|
+
- @output_file.write display
|
254
|
+
- end
|
255
|
+
- end
|
256
|
+
-
|
257
|
+
- pre do |global, _, options, _|
|
258
|
+
- @config = interpret_config_option(global[:config])
|
259
|
+
- @output_file = interpret_output_file_option(options[:output])
|
260
|
+
-
|
261
|
+
- true
|
262
|
+
- end
|
263
|
+
-
|
264
|
+
- class << self
|
265
|
+
- protected
|
266
|
+
-
|
267
|
+
- def interpret_config_option(fname)
|
268
|
+
- AppMap.initialize fname
|
269
|
+
- end
|
270
|
+
-
|
271
|
+
- def interpret_output_file_option(file_name)
|
272
|
+
- Hash.new { |_, fname| -> { File.new(fname, 'w') } }.tap do |open_output_file|
|
273
|
+
- open_output_file[nil] = -> { nil }
|
274
|
+
- open_output_file['-'] = -> { $stdout }
|
275
|
+
- end[file_name].call
|
276
|
+
- end
|
277
|
+
- end
|
278
|
+
- end
|
279
|
+
-end
|
280
|
+
-
|
281
|
+
-exit AppMap::App.run(ARGV)
|
282
|
+
diff --git a/lib/appmap.rb b/lib/appmap.rb
|
283
|
+
index ccf7bb5..998513c 100644
|
284
|
+
--- a/lib/appmap.rb
|
285
|
+
+++ b/lib/appmap.rb
|
286
|
+
@@ -43,6 +43,7 @@ module AppMap
|
287
|
+
# Call this function before the program code is loaded by the Ruby VM, otherwise
|
288
|
+
# the load events won't be seen and the hooks won't activate.
|
289
|
+
def initialize(config_file_path = 'appmap.yml')
|
290
|
+
+ raise "AppMap configuration file #{config_file_path} does not exist" unless ::File.exists?(config_file_path)
|
291
|
+
warn "Configuring AppMap from path #{config_file_path}"
|
292
|
+
Config.load_from_file(config_file_path).tap do |configuration|
|
293
|
+
self.configuration = configuration
|
294
|
+
@@ -50,11 +51,6 @@ module AppMap
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
- # Whether to include source and comments in all class maps.
|
299
|
+
- def include_source?
|
300
|
+
- ENV['APPMAP_SOURCE'] == 'true'
|
301
|
+
- end
|
302
|
+
-
|
303
|
+
# Used to start tracing, stop tracing, and record events.
|
304
|
+
def tracing
|
305
|
+
@tracing ||= Trace::Tracing.new
|
306
|
+
@@ -88,8 +84,8 @@ module AppMap
|
307
|
+
end
|
308
|
+
|
309
|
+
# Builds a class map from a config and a list of Ruby methods.
|
310
|
+
- def class_map(methods, options = {})
|
311
|
+
- ClassMap.build_from_methods(methods, options)
|
312
|
+
+ def class_map(methods)
|
313
|
+
+ ClassMap.build_from_methods(methods)
|
314
|
+
end
|
315
|
+
|
316
|
+
# Returns default metadata detected from the Ruby system and from the
|
317
|
+
diff --git a/lib/appmap/class_map.rb b/lib/appmap/class_map.rb
|
318
|
+
index 4d19a27..0023c1c 100644
|
319
|
+
--- a/lib/appmap/class_map.rb
|
320
|
+
+++ b/lib/appmap/class_map.rb
|
321
|
+
@@ -71,17 +71,17 @@ module AppMap
|
322
|
+
end
|
323
|
+
|
324
|
+
class << self
|
325
|
+
- def build_from_methods(methods, options = {})
|
326
|
+
+ def build_from_methods(methods)
|
327
|
+
root = Types::Root.new
|
328
|
+
methods.each do |method|
|
329
|
+
- add_function root, method, options
|
330
|
+
+ add_function root, method
|
331
|
+
end
|
332
|
+
root.children.map(&:to_h)
|
333
|
+
end
|
334
|
+
|
335
|
+
protected
|
336
|
+
|
337
|
+
- def add_function(root, method, include_source: true)
|
338
|
+
+ def add_function(root, method)
|
339
|
+
package = method.package
|
340
|
+
static = method.static
|
341
|
+
|
342
|
+
@@ -113,16 +113,13 @@ module AppMap
|
343
|
+
[ method.defined_class, static ? '.' : '#', method.name ].join
|
344
|
+
end
|
345
|
+
|
346
|
+
- source, comment = begin
|
347
|
+
- [ method.source, method.comment ]
|
348
|
+
+ comment = begin
|
349
|
+
+ method.comment
|
350
|
+
rescue MethodSource::SourceNotFoundError
|
351
|
+
- [ nil, nil, ]
|
352
|
+
+ nil
|
353
|
+
end
|
354
|
+
|
355
|
+
- if include_source
|
356
|
+
- function_info[:source] = source unless source.blank?
|
357
|
+
- function_info[:comment] = comment unless comment.blank?
|
358
|
+
- end
|
359
|
+
+ function_info[:comment] = comment unless comment.blank?
|
360
|
+
|
361
|
+
function_info[:labels] = parse_labels(comment) + (package.labels || [])
|
362
|
+
object_infos << function_info
|
363
|
+
diff --git a/lib/appmap/command/record.rb b/lib/appmap/command/record.rb
|
364
|
+
index 5f22903..d683f63 100644
|
365
|
+
--- a/lib/appmap/command/record.rb
|
366
|
+
+++ b/lib/appmap/command/record.rb
|
367
|
+
@@ -27,7 +27,7 @@ module AppMap
|
368
|
+
event_thread.join
|
369
|
+
yield AppMap::APPMAP_FORMAT_VERSION,
|
370
|
+
AppMap.detect_metadata,
|
371
|
+
- AppMap.class_map(tracer.event_methods, include_source: AppMap.include_source?),
|
372
|
+
+ AppMap.class_map(tracer.event_methods),
|
373
|
+
events
|
374
|
+
end
|
375
|
+
|
376
|
+
diff --git a/lib/appmap/config.rb b/lib/appmap/config.rb
|
377
|
+
index aa208d6..b4c8841 100644
|
378
|
+
--- a/lib/appmap/config.rb
|
379
|
+
+++ b/lib/appmap/config.rb
|
380
|
+
@@ -49,44 +49,89 @@ module AppMap
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
- Hook = Struct.new(:method_names, :package) do
|
385
|
+
+ Function = Struct.new(:package, :cls, :labels, :function_names) do
|
386
|
+
+ def to_h
|
387
|
+
+ {
|
388
|
+
+ package: package,
|
389
|
+
+ class: cls,
|
390
|
+
+ labels: labels,
|
391
|
+
+ functions: function_names.map(&:to_sym)
|
392
|
+
+ }.compact
|
393
|
+
+ end
|
394
|
+
end
|
395
|
+
|
396
|
+
- OPENSSL_PACKAGES = Package.build_from_path('openssl', package_name: 'openssl', labels: %w[security crypto])
|
397
|
+
+ class Hook
|
398
|
+
+ attr_reader :method_names, :package
|
399
|
+
+
|
400
|
+
+ def initialize(method_names, package)
|
401
|
+
+ @method_names = method_names
|
402
|
+
+ @package = package
|
403
|
+
+ end
|
404
|
+
+
|
405
|
+
+ def to_h
|
406
|
+
+ {
|
407
|
+
+ package: package.name,
|
408
|
+
+ method_names: method_names
|
409
|
+
+ }
|
410
|
+
+ end
|
411
|
+
+ end
|
412
|
+
+
|
413
|
+
+ OPENSSL_PACKAGES = ->(labels) { Package.build_from_path('openssl', package_name: 'openssl', labels: labels) }
|
414
|
+
|
415
|
+
# Methods that should always be hooked, with their containing
|
416
|
+
# package and labels that should be applied to them.
|
417
|
+
HOOKED_METHODS = {
|
418
|
+
- 'ActiveSupport::SecurityUtils' => Hook.new(:secure_compare, Package.build_from_path('active_support', labels: %w[provider.secure_compare])),
|
419
|
+
+ 'ActiveSupport::SecurityUtils' => Hook.new(:secure_compare, Package.build_from_path('active_support', labels: %w[crypto.secure_compare])),
|
420
|
+
'ActionView::Renderer' => Hook.new(:render, Package.build_from_path('action_view', labels: %w[mvc.view])),
|
421
|
+
- 'ActionDispatch::Cookies::CookieJar' => Hook.new(%i[[]= clear update delete recycle], Package.build_from_path('action_pack', labels: %w[provider.http.cookie])),
|
422
|
+
- 'ActionDispatch::Cookies::EncryptedCookieJar' => Hook.new(%i[[]=], Package.build_from_path('action_pack', labels: %w[provider.http.cookie crypto])),
|
423
|
+
- 'CanCan::ControllerAdditions' => Hook.new(%i[authorize! can? cannot?], Package.build_from_path('cancancan', labels: %w[provider.authorization])),
|
424
|
+
- 'CanCan::Ability' => Hook.new(%i[authorize!], Package.build_from_path('cancancan', labels: %w[provider.authorization])),
|
425
|
+
+ 'ActionDispatch::Request::Session' => Hook.new(%i[destroy [] dig values []= clear update delete fetch merge], Package.build_from_path('action_pack', labels: %w[http.session])),
|
426
|
+
+ 'ActionDispatch::Cookies::CookieJar' => Hook.new(%i[[]= clear update delete recycle], Package.build_from_path('action_pack', labels: %w[http.cookie])),
|
427
|
+
+ 'ActionDispatch::Cookies::EncryptedCookieJar' => Hook.new(%i[[]=], Package.build_from_path('action_pack', labels: %w[http.cookie crypto.encrypt])),
|
428
|
+
+ 'CanCan::ControllerAdditions' => Hook.new(%i[authorize! can? cannot?], Package.build_from_path('cancancan', labels: %w[security.authorization])),
|
429
|
+
+ 'CanCan::Ability' => Hook.new(%i[authorize!], Package.build_from_path('cancancan', labels: %w[security.authorization])),
|
430
|
+
+ 'ActionController::Instrumentation' => [
|
431
|
+
+ Hook.new(%i[process_action send_file send_data redirect_to], Package.build_from_path('action_view', labels: %w[mvc.controller])),
|
432
|
+
+ Hook.new(%i[render], Package.build_from_path('action_view', labels: %w[mvc.view])),
|
433
|
+
+ ]
|
434
|
+
}.freeze
|
435
|
+
|
436
|
+
BUILTIN_METHODS = {
|
437
|
+
- 'OpenSSL::PKey::PKey' => Hook.new(:sign, OPENSSL_PACKAGES),
|
438
|
+
- 'OpenSSL::X509::Request' => Hook.new(%i[sign verify], OPENSSL_PACKAGES),
|
439
|
+
- 'OpenSSL::PKCS5' => Hook.new(%i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGES),
|
440
|
+
- 'OpenSSL::Cipher' => Hook.new(%i[encrypt decrypt final], OPENSSL_PACKAGES),
|
441
|
+
- 'OpenSSL::X509::Certificate' => Hook.new(:sign, OPENSSL_PACKAGES),
|
442
|
+
- 'Net::HTTP' => Hook.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[protocol.http io])),
|
443
|
+
- 'Net::SMTP' => Hook.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[protocol.smtp protocol.email io])),
|
444
|
+
- 'Net::POP3' => Hook.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[protocol.pop protocol.email io])),
|
445
|
+
- 'Net::IMAP' => Hook.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[protocol.imap protocol.email io])),
|
446
|
+
- 'Marshal' => Hook.new(%i[dump load], Package.build_from_path('marshal', labels: %w[format.marshal provider.serialization])),
|
447
|
+
- 'Psych' => Hook.new(%i[dump dump_stream load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml provider.serialization])),
|
448
|
+
- 'JSON::Ext::Parser' => Hook.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[format.json provider.serialization])),
|
449
|
+
- 'JSON::Ext::Generator::State' => Hook.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json provider.serialization])),
|
450
|
+
+ 'OpenSSL::PKey::PKey' => Hook.new(:sign, OPENSSL_PACKAGES.(%w[crypto.pkey])),
|
451
|
+
+ 'OpenSSL::X509::Request' => Hook.new(%i[sign verify], OPENSSL_PACKAGES.(%w[crypto.x509])),
|
452
|
+
+ 'OpenSSL::PKCS5' => Hook.new(%i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGES.(%w[crypto.pkcs5])),
|
453
|
+
+ 'OpenSSL::Cipher' => [
|
454
|
+
+ Hook.new(%i[encrypt], OPENSSL_PACKAGES.(%w[crypto.encrypt])),
|
455
|
+
+ Hook.new(%i[decrypt], OPENSSL_PACKAGES.(%w[crypto.decrypt]))
|
456
|
+
+ ],
|
457
|
+
+ 'ActiveSupport::Callbacks::CallbackSequence' => [
|
458
|
+
+ Hook.new(:invoke_before, Package.build_from_path('active_support', package_name: 'active_support', labels: %w[mvc.before_action])),
|
459
|
+
+ Hook.new(:invoke_after, Package.build_from_path('active_support', package_name: 'active_support', labels: %w[mvc.after_action])),
|
460
|
+
+ ],
|
461
|
+
+ 'OpenSSL::X509::Certificate' => Hook.new(:sign, OPENSSL_PACKAGES.(%w[crypto.x509])),
|
462
|
+
+ 'Net::HTTP' => Hook.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[protocol.http])),
|
463
|
+
+ 'Net::SMTP' => Hook.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[protocol.email.smtp])),
|
464
|
+
+ 'Net::POP3' => Hook.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[protocol.email.pop])),
|
465
|
+
+ 'Net::IMAP' => Hook.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[protocol.email.imap])),
|
466
|
+
+ 'Marshal' => Hook.new(%i[dump load], Package.build_from_path('marshal', labels: %w[format.marshal])),
|
467
|
+
+ 'Psych' => Hook.new(%i[dump dump_stream load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml])),
|
468
|
+
+ 'JSON::Ext::Parser' => Hook.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[format.json])),
|
469
|
+
+ 'JSON::Ext::Generator::State' => Hook.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json])),
|
470
|
+
}.freeze
|
471
|
+
|
472
|
+
- attr_reader :name, :packages, :exclude
|
473
|
+
+ attr_reader :name, :packages, :exclude, :builtin_methods
|
474
|
+
|
475
|
+
- def initialize(name, packages = [], exclude = [])
|
476
|
+
+ def initialize(name, packages, exclude: [], functions: [])
|
477
|
+
@name = name
|
478
|
+
@packages = packages
|
479
|
+
@exclude = exclude
|
480
|
+
+ @builtin_methods = BUILTIN_METHODS
|
481
|
+
+ @functions = functions
|
482
|
+
+ @hooked_methods = HOOKED_METHODS.dup
|
483
|
+
+ functions.each do |func|
|
484
|
+
+ package_options = {}
|
485
|
+
+ package_options[:labels] = func.labels if func.labels
|
486
|
+
+ @hooked_methods[func.cls] ||= []
|
487
|
+
+ @hooked_methods[func.cls] << Hook.new(func.function_names, Package.build_from_path(func.package, package_options))
|
488
|
+
+ end
|
489
|
+
end
|
490
|
+
|
491
|
+
class << self
|
492
|
+
@@ -98,6 +143,16 @@ module AppMap
|
493
|
+
|
494
|
+
# Loads configuration from a Hash.
|
495
|
+
def load(config_data)
|
496
|
+
+ functions = (config_data['functions'] || []).map do |function_data|
|
497
|
+
+ package = function_data['package']
|
498
|
+
+ cls = function_data['class']
|
499
|
+
+ functions = function_data['function'] || function_data['functions']
|
500
|
+
+ raise 'AppMap class configuration should specify package, class and function(s)' unless package && cls && functions
|
501
|
+
+ functions = Array(functions).map(&:to_sym)
|
502
|
+
+ labels = function_data['label'] || function_data['labels']
|
503
|
+
+ labels = Array(labels).map(&:to_s) if labels
|
504
|
+
+ Function.new(package, cls, labels, functions)
|
505
|
+
+ end
|
506
|
+
packages = (config_data['packages'] || []).map do |package|
|
507
|
+
gem = package['gem']
|
508
|
+
path = package['path']
|
509
|
+
@@ -112,7 +167,8 @@ module AppMap
|
510
|
+
Package.build_from_path(path, exclude: package['exclude'] || [], shallow: package['shallow'])
|
511
|
+
end
|
512
|
+
end.compact
|
513
|
+
- Config.new config_data['name'], packages, config_data['exclude'] || []
|
514
|
+
+ exclude = config_data['exclude'] || []
|
515
|
+
+ Config.new config_data['name'], packages, exclude: exclude, functions: functions
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
@@ -120,6 +176,7 @@ module AppMap
|
520
|
+
{
|
521
|
+
name: name,
|
522
|
+
packages: packages.map(&:to_h),
|
523
|
+
+ functions: @functions.map(&:to_h),
|
524
|
+
exclude: exclude
|
525
|
+
}
|
526
|
+
end
|
527
|
+
@@ -164,14 +221,17 @@ module AppMap
|
528
|
+
end
|
529
|
+
|
530
|
+
def find_package(defined_class, method_name)
|
531
|
+
- hook = find_hook(defined_class)
|
532
|
+
- return nil unless hook
|
533
|
+
+ hooks = find_hooks(defined_class)
|
534
|
+
+ return nil unless hooks
|
535
|
+
|
536
|
+
- Array(hook.method_names).include?(method_name) ? hook.package : nil
|
537
|
+
+ hook = Array(hooks).find do |hook|
|
538
|
+
+ Array(hook.method_names).include?(method_name)
|
539
|
+
+ end
|
540
|
+
+ hook ? hook.package : nil
|
541
|
+
end
|
542
|
+
|
543
|
+
- def find_hook(defined_class)
|
544
|
+
- HOOKED_METHODS[defined_class] || BUILTIN_METHODS[defined_class]
|
545
|
+
+ def find_hooks(defined_class)
|
546
|
+
+ Array(@hooked_methods[defined_class] || @builtin_methods[defined_class])
|
547
|
+
end
|
548
|
+
end
|
549
|
+
end
|
550
|
+
diff --git a/lib/appmap/cucumber.rb b/lib/appmap/cucumber.rb
|
551
|
+
index 7b59c0a..eaf4ac3 100644
|
552
|
+
--- a/lib/appmap/cucumber.rb
|
553
|
+
+++ b/lib/appmap/cucumber.rb
|
554
|
+
@@ -50,7 +50,7 @@ module AppMap
|
555
|
+
appmap['metadata'] = update_metadata(scenario, appmap['metadata'])
|
556
|
+
scenario_filename = AppMap::Util.scenario_filename(appmap['metadata']['name'])
|
557
|
+
|
558
|
+
- File.write(File.join(APPMAP_OUTPUT_DIR, scenario_filename), JSON.generate(appmap))
|
559
|
+
+ AppMap::Util.write_appmap(File.join(APPMAP_OUTPUT_DIR, scenario_filename), JSON.generate(appmap))
|
560
|
+
end
|
561
|
+
|
562
|
+
def enabled?
|
563
|
+
diff --git a/lib/appmap/hook.rb b/lib/appmap/hook.rb
|
564
|
+
index dd919d9..f141ac7 100644
|
565
|
+
--- a/lib/appmap/hook.rb
|
566
|
+
+++ b/lib/appmap/hook.rb
|
567
|
+
@@ -32,6 +32,7 @@ module AppMap
|
568
|
+
end
|
569
|
+
|
570
|
+
attr_reader :config
|
571
|
+
+
|
572
|
+
def initialize(config)
|
573
|
+
@config = config
|
574
|
+
end
|
575
|
+
@@ -59,6 +60,10 @@ module AppMap
|
576
|
+
|
577
|
+
hook = lambda do |hook_cls|
|
578
|
+
lambda do |method_id|
|
579
|
+
+ # Don't try and trace the AppMap methods or there will be
|
580
|
+
+ # a stack overflow in the defined hook method.
|
581
|
+
+ return if (hook_cls&.name || '').split('::')[0] == AppMap.name
|
582
|
+
+
|
583
|
+
method = begin
|
584
|
+
hook_cls.public_instance_method(method_id)
|
585
|
+
rescue NameError
|
586
|
+
@@ -78,11 +83,9 @@ module AppMap
|
587
|
+
config.always_hook?(hook_cls, method.name) ||
|
588
|
+
config.included_by_location?(method)
|
589
|
+
|
590
|
+
- hook_method = Hook::Method.new(config.package_for_method(method), hook_cls, method)
|
591
|
+
+ package = config.package_for_method(method)
|
592
|
+
|
593
|
+
- # Don't try and trace the AppMap methods or there will be
|
594
|
+
- # a stack overflow in the defined hook method.
|
595
|
+
- next if /\AAppMap[:\.]/.match?(hook_method.method_display_name)
|
596
|
+
+ hook_method = Hook::Method.new(package, hook_cls, method)
|
597
|
+
|
598
|
+
hook_method.activate
|
599
|
+
end
|
600
|
+
@@ -112,25 +115,27 @@ module AppMap
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
- Config::BUILTIN_METHODS.each do |class_name, hook|
|
605
|
+
- require hook.package.package_name if hook.package.package_name
|
606
|
+
- Array(hook.method_names).each do |method_name|
|
607
|
+
- method_name = method_name.to_sym
|
608
|
+
+ config.builtin_methods.each do |class_name, hooks|
|
609
|
+
+ Array(hooks).each do |hook|
|
610
|
+
+ require hook.package.package_name if hook.package.package_name
|
611
|
+
+ Array(hook.method_names).each do |method_name|
|
612
|
+
+ method_name = method_name.to_sym
|
613
|
+
|
614
|
+
- cls = class_from_string.(class_name)
|
615
|
+
- method = \
|
616
|
+
- begin
|
617
|
+
- cls.instance_method(method_name)
|
618
|
+
- rescue NameError
|
619
|
+
- cls.method(method_name) rescue nil
|
620
|
+
- end
|
621
|
+
+ cls = class_from_string.(class_name)
|
622
|
+
+ method = \
|
623
|
+
+ begin
|
624
|
+
+ cls.instance_method(method_name)
|
625
|
+
+ rescue NameError
|
626
|
+
+ cls.method(method_name) rescue nil
|
627
|
+
+ end
|
628
|
+
|
629
|
+
- next if config.never_hook?(method)
|
630
|
+
+ next if config.never_hook?(method)
|
631
|
+
|
632
|
+
- if method
|
633
|
+
- Hook::Method.new(hook.package, cls, method).activate
|
634
|
+
- else
|
635
|
+
- warn "Method #{method_name} not found on #{cls.name}"
|
636
|
+
+ if method
|
637
|
+
+ Hook::Method.new(hook.package, cls, method).activate
|
638
|
+
+ else
|
639
|
+
+ warn "Method #{method_name} not found on #{cls.name}"
|
640
|
+
+ end
|
641
|
+
end
|
642
|
+
end
|
643
|
+
end
|
644
|
+
diff --git a/lib/appmap/middleware/remote_recording.rb b/lib/appmap/middleware/remote_recording.rb
|
645
|
+
index 83affc0..f695d25 100644
|
646
|
+
--- a/lib/appmap/middleware/remote_recording.rb
|
647
|
+
+++ b/lib/appmap/middleware/remote_recording.rb
|
648
|
+
@@ -67,7 +67,7 @@ module AppMap
|
649
|
+
|
650
|
+
response = JSON.generate \
|
651
|
+
version: AppMap::APPMAP_FORMAT_VERSION,
|
652
|
+
- classMap: AppMap.class_map(tracer.event_methods, include_source: AppMap.include_source?),
|
653
|
+
+ classMap: AppMap.class_map(tracer.event_methods),
|
654
|
+
metadata: metadata,
|
655
|
+
events: @events
|
656
|
+
|
657
|
+
diff --git a/lib/appmap/minitest.rb b/lib/appmap/minitest.rb
|
658
|
+
index dc88bac..cf4a4d2 100644
|
659
|
+
--- a/lib/appmap/minitest.rb
|
660
|
+
+++ b/lib/appmap/minitest.rb
|
661
|
+
@@ -26,8 +26,9 @@ module AppMap
|
662
|
+
end
|
663
|
+
|
664
|
+
|
665
|
+
- def finish
|
666
|
+
+ def finish(exception)
|
667
|
+
warn "Finishing recording of test #{test.class}.#{test.name}" if AppMap::Minitest::LOG
|
668
|
+
+ warn "Exception: #{exception}" if exception && AppMap::Minitest::LOG
|
669
|
+
|
670
|
+
events = []
|
671
|
+
AppMap.tracing.delete @trace
|
672
|
+
@@ -36,15 +37,17 @@ module AppMap
|
673
|
+
|
674
|
+
AppMap::Minitest.add_event_methods @trace.event_methods
|
675
|
+
|
676
|
+
- class_map = AppMap.class_map(@trace.event_methods, include_source: AppMap.include_source?)
|
677
|
+
+ class_map = AppMap.class_map(@trace.event_methods)
|
678
|
+
|
679
|
+
feature_group = test.class.name.underscore.split('_')[0...-1].join('_').capitalize
|
680
|
+
feature_name = test.name.split('_')[1..-1].join(' ')
|
681
|
+
scenario_name = [ feature_group, feature_name ].join(' ')
|
682
|
+
|
683
|
+
- AppMap::Minitest.save scenario_name,
|
684
|
+
- class_map,
|
685
|
+
- source_location,
|
686
|
+
+ AppMap::Minitest.save name: scenario_name,
|
687
|
+
+ class_map: class_map,
|
688
|
+
+ source_location: source_location,
|
689
|
+
+ test_status: exception ? 'failed' : 'succeeded',
|
690
|
+
+ exception: exception,
|
691
|
+
events: events
|
692
|
+
end
|
693
|
+
end
|
694
|
+
@@ -63,11 +66,11 @@ module AppMap
|
695
|
+
@recordings_by_test[test.object_id] = Recording.new(test, name)
|
696
|
+
end
|
697
|
+
|
698
|
+
- def end_test(test)
|
699
|
+
+ def end_test(test, exception:)
|
700
|
+
recording = @recordings_by_test.delete(test.object_id)
|
701
|
+
return warn "No recording found for #{test}" unless recording
|
702
|
+
|
703
|
+
- recording.finish
|
704
|
+
+ recording.finish exception
|
705
|
+
end
|
706
|
+
|
707
|
+
def config
|
708
|
+
@@ -78,9 +81,9 @@ module AppMap
|
709
|
+
@event_methods += event_methods
|
710
|
+
end
|
711
|
+
|
712
|
+
- def save(example_name, class_map, source_location, events: nil, labels: nil)
|
713
|
+
+ def save(name:, class_map:, source_location:, test_status:, exception:, events:)
|
714
|
+
metadata = AppMap::Minitest.metadata.tap do |m|
|
715
|
+
- m[:name] = example_name
|
716
|
+
+ m[:name] = name
|
717
|
+
m[:source_location] = source_location
|
718
|
+
m[:app] = AppMap.configuration.name
|
719
|
+
m[:frameworks] ||= []
|
720
|
+
@@ -91,6 +94,13 @@ module AppMap
|
721
|
+
m[:recorder] = {
|
722
|
+
name: 'minitest'
|
723
|
+
}
|
724
|
+
+ m[:test_status] = test_status
|
725
|
+
+ if exception
|
726
|
+
+ m[:exception] = {
|
727
|
+
+ class: exception.class.name,
|
728
|
+
+ message: exception.to_s
|
729
|
+
+ }
|
730
|
+
+ end
|
731
|
+
end
|
732
|
+
|
733
|
+
appmap = {
|
734
|
+
@@ -99,14 +109,9 @@ module AppMap
|
735
|
+
classMap: class_map,
|
736
|
+
events: events
|
737
|
+
}.compact
|
738
|
+
- fname = AppMap::Util.scenario_filename(example_name)
|
739
|
+
+ fname = AppMap::Util.scenario_filename(name)
|
740
|
+
|
741
|
+
- File.write(File.join(APPMAP_OUTPUT_DIR, fname), JSON.generate(appmap))
|
742
|
+
- end
|
743
|
+
-
|
744
|
+
- def print_inventory
|
745
|
+
- class_map = AppMap.class_map(@event_methods)
|
746
|
+
- save 'Inventory', class_map, labels: %w[inventory]
|
747
|
+
+ AppMap::Util.write_appmap(File.join(APPMAP_OUTPUT_DIR, fname), JSON.generate(appmap))
|
748
|
+
end
|
749
|
+
|
750
|
+
def enabled?
|
751
|
+
@@ -115,9 +120,6 @@ module AppMap
|
752
|
+
|
753
|
+
def run
|
754
|
+
init
|
755
|
+
- at_exit do
|
756
|
+
- print_inventory
|
757
|
+
- end
|
758
|
+
end
|
759
|
+
end
|
760
|
+
end
|
761
|
+
@@ -135,7 +137,7 @@ if AppMap::Minitest.enabled?
|
762
|
+
begin
|
763
|
+
run_without_hook
|
764
|
+
ensure
|
765
|
+
- AppMap::Minitest.end_test self
|
766
|
+
+ AppMap::Minitest.end_test self, exception: $!
|
767
|
+
end
|
768
|
+
end
|
769
|
+
end
|
770
|
+
diff --git a/lib/appmap/record.rb b/lib/appmap/record.rb
|
771
|
+
index f42da60..bb3ea94 100644
|
772
|
+
--- a/lib/appmap/record.rb
|
773
|
+
+++ b/lib/appmap/record.rb
|
774
|
+
@@ -23,5 +23,5 @@ at_exit do
|
775
|
+
'classMap' => AppMap.class_map(tracer.event_methods),
|
776
|
+
'events' => events
|
777
|
+
}
|
778
|
+
- File.write 'appmap.json', JSON.generate(appmap)
|
779
|
+
+ AppMap::Util.write_appmap('appmap.json', JSON.generate(appmap))
|
780
|
+
end
|
781
|
+
diff --git a/lib/appmap/rspec.rb b/lib/appmap/rspec.rb
|
782
|
+
index bbc3781..79c0c66 100644
|
783
|
+
--- a/lib/appmap/rspec.rb
|
784
|
+
+++ b/lib/appmap/rspec.rb
|
785
|
+
@@ -94,8 +94,9 @@ module AppMap
|
786
|
+
result
|
787
|
+
end
|
788
|
+
|
789
|
+
- def finish
|
790
|
+
+ def finish(exception)
|
791
|
+
warn "Finishing recording of example #{example}" if AppMap::RSpec::LOG
|
792
|
+
+ warn "Exception: #{exception}" if exception && AppMap::RSpec::LOG
|
793
|
+
|
794
|
+
events = []
|
795
|
+
AppMap.tracing.delete @trace
|
796
|
+
@@ -104,7 +105,7 @@ module AppMap
|
797
|
+
|
798
|
+
AppMap::RSpec.add_event_methods @trace.event_methods
|
799
|
+
|
800
|
+
- class_map = AppMap.class_map(@trace.event_methods, include_source: AppMap.include_source?)
|
801
|
+
+ class_map = AppMap.class_map(@trace.event_methods)
|
802
|
+
|
803
|
+
description = []
|
804
|
+
scope = ScopeExample.new(example)
|
805
|
+
@@ -127,9 +128,11 @@ module AppMap
|
806
|
+
|
807
|
+
full_description = normalize.call(description.join(' '))
|
808
|
+
|
809
|
+
- AppMap::RSpec.save full_description,
|
810
|
+
- class_map,
|
811
|
+
- source_location,
|
812
|
+
+ AppMap::RSpec.save name: full_description,
|
813
|
+
+ class_map: class_map,
|
814
|
+
+ source_location: source_location,
|
815
|
+
+ test_status: exception ? 'failed' : 'succeeded',
|
816
|
+
+ exception: exception,
|
817
|
+
events: events
|
818
|
+
end
|
819
|
+
end
|
820
|
+
@@ -148,11 +151,11 @@ module AppMap
|
821
|
+
@recordings_by_example[example.object_id] = Recording.new(example)
|
822
|
+
end
|
823
|
+
|
824
|
+
- def end_spec(example)
|
825
|
+
+ def end_spec(example, exception:)
|
826
|
+
recording = @recordings_by_example.delete(example.object_id)
|
827
|
+
return warn "No recording found for #{example}" unless recording
|
828
|
+
|
829
|
+
- recording.finish
|
830
|
+
+ recording.finish exception
|
831
|
+
end
|
832
|
+
|
833
|
+
def config
|
834
|
+
@@ -163,12 +166,11 @@ module AppMap
|
835
|
+
@event_methods += event_methods
|
836
|
+
end
|
837
|
+
|
838
|
+
- def save(example_name, class_map, source_location, events: nil, labels: nil)
|
839
|
+
+ def save(name:, class_map:, source_location:, test_status:, exception:, events:)
|
840
|
+
metadata = AppMap::RSpec.metadata.tap do |m|
|
841
|
+
- m[:name] = example_name
|
842
|
+
+ m[:name] = name
|
843
|
+
m[:source_location] = source_location
|
844
|
+
m[:app] = AppMap.configuration.name
|
845
|
+
- m[:labels] = labels if labels
|
846
|
+
m[:frameworks] ||= []
|
847
|
+
m[:frameworks] << {
|
848
|
+
name: 'rspec',
|
849
|
+
@@ -177,6 +179,13 @@ module AppMap
|
850
|
+
m[:recorder] = {
|
851
|
+
name: 'rspec'
|
852
|
+
}
|
853
|
+
+ m[:test_status] = test_status
|
854
|
+
+ if exception
|
855
|
+
+ m[:exception] = {
|
856
|
+
+ class: exception.class.name,
|
857
|
+
+ message: exception.to_s
|
858
|
+
+ }
|
859
|
+
+ end
|
860
|
+
end
|
861
|
+
|
862
|
+
appmap = {
|
863
|
+
@@ -185,14 +194,9 @@ module AppMap
|
864
|
+
classMap: class_map,
|
865
|
+
events: events
|
866
|
+
}.compact
|
867
|
+
- fname = AppMap::Util.scenario_filename(example_name)
|
868
|
+
-
|
869
|
+
- File.write(File.join(APPMAP_OUTPUT_DIR, fname), JSON.generate(appmap))
|
870
|
+
- end
|
871
|
+
+ fname = AppMap::Util.scenario_filename(name)
|
872
|
+
|
873
|
+
- def print_inventory
|
874
|
+
- class_map = AppMap.class_map(@event_methods)
|
875
|
+
- save 'Inventory', class_map, labels: %w[inventory]
|
876
|
+
+ AppMap::Util.write_appmap(File.join(APPMAP_OUTPUT_DIR, fname), JSON.generate(appmap))
|
877
|
+
end
|
878
|
+
|
879
|
+
def enabled?
|
880
|
+
@@ -201,9 +205,6 @@ module AppMap
|
881
|
+
|
882
|
+
def run
|
883
|
+
init
|
884
|
+
- at_exit do
|
885
|
+
- print_inventory
|
886
|
+
- end
|
887
|
+
end
|
888
|
+
end
|
889
|
+
end
|
890
|
+
@@ -225,7 +226,7 @@ if AppMap::RSpec.enabled?
|
891
|
+
begin
|
892
|
+
instance_exec(&fn)
|
893
|
+
ensure
|
894
|
+
- AppMap::RSpec.end_spec example
|
895
|
+
+ AppMap::RSpec.end_spec example, exception: $!
|
896
|
+
end
|
897
|
+
end
|
898
|
+
end
|
899
|
+
diff --git a/lib/appmap/util.rb b/lib/appmap/util.rb
|
900
|
+
index 133d54a..cd08ded 100644
|
901
|
+
--- a/lib/appmap/util.rb
|
902
|
+
+++ b/lib/appmap/util.rb
|
903
|
+
@@ -71,6 +71,22 @@ module AppMap
|
904
|
+
|
905
|
+
event
|
906
|
+
end
|
907
|
+
+
|
908
|
+
+ # Atomically writes AppMap data to +filename+.
|
909
|
+
+ def write_appmap(filename, appmap)
|
910
|
+
+ require 'fileutils'
|
911
|
+
+ require 'tmpdir'
|
912
|
+
+
|
913
|
+
+ # This is what Ruby Tempfile does; but we don't want the file to be unlinked.
|
914
|
+
+ mode = File::RDWR | File::CREAT | File::EXCL
|
915
|
+
+ ::Dir::Tmpname.create([ 'appmap_', '.json' ]) do |tmpname|
|
916
|
+
+ tempfile = File.open(tmpname, mode)
|
917
|
+
+ tempfile.write(appmap)
|
918
|
+
+ tempfile.close
|
919
|
+
+ # Atomically move the tempfile into place.
|
920
|
+
+ FileUtils.mv tempfile.path, filename
|
921
|
+
+ end
|
922
|
+
+ end
|
923
|
+
end
|
924
|
+
end
|
925
|
+
end
|
926
|
+
diff --git a/lib/appmap/version.rb b/lib/appmap/version.rb
|
927
|
+
index ff7d53b..8471d09 100644
|
928
|
+
--- a/lib/appmap/version.rb
|
929
|
+
+++ b/lib/appmap/version.rb
|
930
|
+
@@ -3,7 +3,7 @@
|
931
|
+
module AppMap
|
932
|
+
URL = 'https://github.com/applandinc/appmap-ruby'
|
933
|
+
|
934
|
+
- VERSION = '0.43.0'
|
935
|
+
+ VERSION = '0.44.0'
|
936
|
+
|
937
|
+
APPMAP_FORMAT_VERSION = '1.4'
|
938
|
+
end
|
939
|
+
diff --git a/spec/abstract_controller_base_spec.rb b/spec/abstract_controller_base_spec.rb
|
940
|
+
index 26e58b1..2aed976 100644
|
941
|
+
--- a/spec/abstract_controller_base_spec.rb
|
942
|
+
+++ b/spec/abstract_controller_base_spec.rb
|
943
|
+
@@ -27,6 +27,8 @@ describe 'Rails' do
|
944
|
+
end
|
945
|
+
|
946
|
+
let(:appmap) { JSON.parse File.read File.join tmpdir, 'appmap/rspec', appmap_json_file }
|
947
|
+
+ let(:appmap_json_path) { File.join(tmpdir, 'appmap/rspec', appmap_json_file) }
|
948
|
+
+ let(:appmap) { JSON.parse File.read(appmap_json_path) }
|
949
|
+
let(:events) { appmap['events'] }
|
950
|
+
|
951
|
+
describe 'an API route' do
|
952
|
+
@@ -35,10 +37,6 @@ describe 'Rails' do
|
953
|
+
'Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json'
|
954
|
+
end
|
955
|
+
|
956
|
+
- it 'inventory file is printed' do
|
957
|
+
- expect(File).to exist(File.join(tmpdir, 'appmap/rspec/Inventory.appmap.json'))
|
958
|
+
- end
|
959
|
+
-
|
960
|
+
it 'http_server_request is recorded in the appmap' do
|
961
|
+
expect(events).to include(
|
962
|
+
hash_including(
|
963
|
+
diff --git a/spec/class_map_spec.rb b/spec/class_map_spec.rb
|
964
|
+
index 343c8b9..4f60708 100644
|
965
|
+
--- a/spec/class_map_spec.rb
|
966
|
+
+++ b/spec/class_map_spec.rb
|
967
|
+
@@ -4,18 +4,10 @@ require 'spec_helper'
|
968
|
+
|
969
|
+
describe 'AppMap::ClassMap' do
|
970
|
+
describe '.build_from_methods' do
|
971
|
+
- it 'includes source code if available' do
|
972
|
+
- map = AppMap.class_map([scoped_method(method(:test_method))])
|
973
|
+
+ it 'includes method comment' do
|
974
|
+
+ map = AppMap.class_map([scoped_method((method :test_method))])
|
975
|
+
function = dig_map(map, 5)[0]
|
976
|
+
- expect(function[:source]).to include 'test method body'
|
977
|
+
- expect(function[:comment]).to include 'test method comment'
|
978
|
+
- end
|
979
|
+
-
|
980
|
+
- it 'can omit source code even if available' do
|
981
|
+
- map = AppMap.class_map([scoped_method((method :test_method))], include_source: false)
|
982
|
+
- function = dig_map(map, 5)[0]
|
983
|
+
- expect(function).to_not include(:source)
|
984
|
+
- expect(function).to_not include(:comment)
|
985
|
+
+ expect(function).to include(:comment)
|
986
|
+
end
|
987
|
+
|
988
|
+
# test method comment
|
989
|
+
diff --git a/spec/config_spec.rb b/spec/config_spec.rb
|
990
|
+
index 5eeaac6..6657f1a 100644
|
991
|
+
--- a/spec/config_spec.rb
|
992
|
+
+++ b/spec/config_spec.rb
|
993
|
+
@@ -17,10 +17,40 @@ describe AppMap::Config, docker: false do
|
994
|
+
path: 'path-2',
|
995
|
+
exclude: [ 'exclude-1' ]
|
996
|
+
}
|
997
|
+
+ ],
|
998
|
+
+ functions: [
|
999
|
+
+ {
|
1000
|
+
+ package: 'pkg',
|
1001
|
+
+ class: 'cls',
|
1002
|
+
+ function: 'fn',
|
1003
|
+
+ label: 'lbl'
|
1004
|
+
+ }
|
1005
|
+
]
|
1006
|
+
}.deep_stringify_keys!
|
1007
|
+
config = AppMap::Config.load(config_data)
|
1008
|
+
|
1009
|
+
- expect(config.to_h.deep_stringify_keys!).to eq(config_data)
|
1010
|
+
+ config_expectation = {
|
1011
|
+
+ exclude: [],
|
1012
|
+
+ name: 'test',
|
1013
|
+
+ packages: [
|
1014
|
+
+ {
|
1015
|
+
+ path: 'path-1'
|
1016
|
+
+ },
|
1017
|
+
+ {
|
1018
|
+
+ path: 'path-2',
|
1019
|
+
+ exclude: [ 'exclude-1' ]
|
1020
|
+
+ }
|
1021
|
+
+ ],
|
1022
|
+
+ functions: [
|
1023
|
+
+ {
|
1024
|
+
+ package: 'pkg',
|
1025
|
+
+ class: 'cls',
|
1026
|
+
+ functions: [ :fn ],
|
1027
|
+
+ labels: ['lbl']
|
1028
|
+
+ }
|
1029
|
+
+ ]
|
1030
|
+
+ }.deep_stringify_keys!
|
1031
|
+
+
|
1032
|
+
+ expect(config.to_h.deep_stringify_keys!).to eq(config_expectation)
|
1033
|
+
end
|
1034
|
+
end
|
1035
|
+
diff --git a/spec/fixtures/hook/custom_instance_method.rb b/spec/fixtures/hook/custom_instance_method.rb
|
1036
|
+
new file mode 100644
|
1037
|
+
index 0000000..285db81
|
1038
|
+
--- /dev/null
|
1039
|
+
+++ b/spec/fixtures/hook/custom_instance_method.rb
|
1040
|
+
@@ -0,0 +1,11 @@
|
1041
|
+
+# frozen_string_literal: true
|
1042
|
+
+
|
1043
|
+
+class CustomInstanceMethod
|
1044
|
+
+ def to_s
|
1045
|
+
+ 'CustomInstance Method fixture'
|
1046
|
+
+ end
|
1047
|
+
+
|
1048
|
+
+ def say_default
|
1049
|
+
+ 'default'
|
1050
|
+
+ end
|
1051
|
+
+end
|
1052
|
+
diff --git a/spec/fixtures/hook/method_named_call.rb b/spec/fixtures/hook/method_named_call.rb
|
1053
|
+
new file mode 100644
|
1054
|
+
index 0000000..69a2cc5
|
1055
|
+
--- /dev/null
|
1056
|
+
+++ b/spec/fixtures/hook/method_named_call.rb
|
1057
|
+
@@ -0,0 +1,11 @@
|
1058
|
+
+# frozen_string_literal: true
|
1059
|
+
+
|
1060
|
+
+class MethodNamedCall
|
1061
|
+
+ def to_s
|
1062
|
+
+ 'MethodNamedCall'
|
1063
|
+
+ end
|
1064
|
+
+
|
1065
|
+
+ def call(a, b, c, d, e)
|
1066
|
+
+ [ a, b, c, d, e ].join(' ')
|
1067
|
+
+ end
|
1068
|
+
+end
|
1069
|
+
diff --git a/spec/hook_spec.rb b/spec/hook_spec.rb
|
1070
|
+
index e4cbc99..8afb9f1 100644
|
1071
|
+
--- a/spec/hook_spec.rb
|
1072
|
+
+++ b/spec/hook_spec.rb
|
1073
|
+
@@ -64,13 +64,144 @@ describe 'AppMap class Hooking', docker: false do
|
1074
|
+
it 'excludes named classes and methods' do
|
1075
|
+
load 'spec/fixtures/hook/exclude.rb'
|
1076
|
+
package = AppMap::Config::Package.build_from_path('spec/fixtures/hook/exclude.rb')
|
1077
|
+
- config = AppMap::Config.new('hook_spec', [ package ], %w[ExcludeTest])
|
1078
|
+
+ config = AppMap::Config.new('hook_spec', [ package ], exclude: %w[ExcludeTest])
|
1079
|
+
AppMap.configuration = config
|
1080
|
+
|
1081
|
+
expect(config.never_hook?(ExcludeTest.new.method(:instance_method))).to be_truthy
|
1082
|
+
expect(config.never_hook?(ExcludeTest.method(:cls_method))).to be_truthy
|
1083
|
+
end
|
1084
|
+
|
1085
|
+
+ it "handles an instance method named 'call' without issues" do
|
1086
|
+
+ events_yaml = <<~YAML
|
1087
|
+
+ ---
|
1088
|
+
+ - :id: 1
|
1089
|
+
+ :event: :call
|
1090
|
+
+ :defined_class: MethodNamedCall
|
1091
|
+
+ :method_id: call
|
1092
|
+
+ :path: spec/fixtures/hook/method_named_call.rb
|
1093
|
+
+ :lineno: 8
|
1094
|
+
+ :static: false
|
1095
|
+
+ :parameters:
|
1096
|
+
+ - :name: :a
|
1097
|
+
+ :class: Integer
|
1098
|
+
+ :value: '1'
|
1099
|
+
+ :kind: :req
|
1100
|
+
+ - :name: :b
|
1101
|
+
+ :class: Integer
|
1102
|
+
+ :value: '2'
|
1103
|
+
+ :kind: :req
|
1104
|
+
+ - :name: :c
|
1105
|
+
+ :class: Integer
|
1106
|
+
+ :value: '3'
|
1107
|
+
+ :kind: :req
|
1108
|
+
+ - :name: :d
|
1109
|
+
+ :class: Integer
|
1110
|
+
+ :value: '4'
|
1111
|
+
+ :kind: :req
|
1112
|
+
+ - :name: :e
|
1113
|
+
+ :class: Integer
|
1114
|
+
+ :value: '5'
|
1115
|
+
+ :kind: :req
|
1116
|
+
+ :receiver:
|
1117
|
+
+ :class: MethodNamedCall
|
1118
|
+
+ :value: MethodNamedCall
|
1119
|
+
+ - :id: 2
|
1120
|
+
+ :event: :return
|
1121
|
+
+ :parent_id: 1
|
1122
|
+
+ :return_value:
|
1123
|
+
+ :class: String
|
1124
|
+
+ :value: 1 2 3 4 5
|
1125
|
+
+ YAML
|
1126
|
+
+
|
1127
|
+
+ _, tracer = test_hook_behavior 'spec/fixtures/hook/method_named_call.rb', events_yaml do
|
1128
|
+
+ expect(MethodNamedCall.new.call(1, 2, 3, 4, 5)).to eq('1 2 3 4 5')
|
1129
|
+
+ end
|
1130
|
+
+ class_map = AppMap.class_map(tracer.event_methods)
|
1131
|
+
+ expect(Diffy::Diff.new(<<~CLASSMAP, YAML.dump(class_map)).to_s).to eq('')
|
1132
|
+
+ ---
|
1133
|
+
+ - :name: spec/fixtures/hook/method_named_call.rb
|
1134
|
+
+ :type: package
|
1135
|
+
+ :children:
|
1136
|
+
+ - :name: MethodNamedCall
|
1137
|
+
+ :type: class
|
1138
|
+
+ :children:
|
1139
|
+
+ - :name: call
|
1140
|
+
+ :type: function
|
1141
|
+
+ :location: spec/fixtures/hook/method_named_call.rb:8
|
1142
|
+
+ :static: false
|
1143
|
+
+ CLASSMAP
|
1144
|
+
+ end
|
1145
|
+
+
|
1146
|
+
+ it 'can custom hook and label a function' do
|
1147
|
+
+ events_yaml = <<~YAML
|
1148
|
+
+ ---
|
1149
|
+
+ - :id: 1
|
1150
|
+
+ :event: :call
|
1151
|
+
+ :defined_class: CustomInstanceMethod
|
1152
|
+
+ :method_id: say_default
|
1153
|
+
+ :path: spec/fixtures/hook/custom_instance_method.rb
|
1154
|
+
+ :lineno: 8
|
1155
|
+
+ :static: false
|
1156
|
+
+ :parameters: []
|
1157
|
+
+ :receiver:
|
1158
|
+
+ :class: CustomInstanceMethod
|
1159
|
+
+ :value: CustomInstance Method fixture
|
1160
|
+
+ - :id: 2
|
1161
|
+
+ :event: :return
|
1162
|
+
+ :parent_id: 1
|
1163
|
+
+ :return_value:
|
1164
|
+
+ :class: String
|
1165
|
+
+ :value: default
|
1166
|
+
+ YAML
|
1167
|
+
+
|
1168
|
+
+ config = AppMap::Config.load({
|
1169
|
+
+ functions: [
|
1170
|
+
+ {
|
1171
|
+
+ package: 'hook_spec',
|
1172
|
+
+ class: 'CustomInstanceMethod',
|
1173
|
+
+ functions: [ :say_default ],
|
1174
|
+
+ labels: ['cowsay']
|
1175
|
+
+ }
|
1176
|
+
+ ]
|
1177
|
+
+ }.deep_stringify_keys)
|
1178
|
+
+
|
1179
|
+
+ load 'spec/fixtures/hook/custom_instance_method.rb'
|
1180
|
+
+ hook_cls = CustomInstanceMethod
|
1181
|
+
+ method = hook_cls.instance_method(:say_default)
|
1182
|
+
+
|
1183
|
+
+ require 'appmap/hook/method'
|
1184
|
+
+ hook_method = AppMap::Hook::Method.new(config.package_for_method(method), hook_cls, method)
|
1185
|
+
+ hook_method.activate
|
1186
|
+
+
|
1187
|
+
+ tracer = AppMap.tracing.trace
|
1188
|
+
+ AppMap::Event.reset_id_counter
|
1189
|
+
+ begin
|
1190
|
+
+ expect(CustomInstanceMethod.new.say_default).to eq('default')
|
1191
|
+
+ ensure
|
1192
|
+
+ AppMap.tracing.delete(tracer)
|
1193
|
+
+ end
|
1194
|
+
+
|
1195
|
+
+ events = collect_events(tracer).to_yaml
|
1196
|
+
+
|
1197
|
+
+ expect(Diffy::Diff.new(events_yaml, events).to_s).to eq('')
|
1198
|
+
+ class_map = AppMap.class_map(tracer.event_methods)
|
1199
|
+
+ expect(Diffy::Diff.new(<<~CLASSMAP, YAML.dump(class_map)).to_s).to eq('')
|
1200
|
+
+ ---
|
1201
|
+
+ - :name: hook_spec
|
1202
|
+
+ :type: package
|
1203
|
+
+ :children:
|
1204
|
+
+ - :name: CustomInstanceMethod
|
1205
|
+
+ :type: class
|
1206
|
+
+ :children:
|
1207
|
+
+ - :name: say_default
|
1208
|
+
+ :type: function
|
1209
|
+
+ :location: spec/fixtures/hook/custom_instance_method.rb:8
|
1210
|
+
+ :static: false
|
1211
|
+
+ :labels:
|
1212
|
+
+ - cowsay
|
1213
|
+
+ CLASSMAP
|
1214
|
+
+ end
|
1215
|
+
+
|
1216
|
+
it 'parses labels from comments' do
|
1217
|
+
_, tracer = invoke_test_file 'spec/fixtures/hook/labels.rb' do
|
1218
|
+
ClassWithLabel.new.fn_with_label
|
1219
|
+
@@ -91,9 +222,6 @@ describe 'AppMap class Hooking', docker: false do
|
1220
|
+
:labels:
|
1221
|
+
- has-fn-label
|
1222
|
+
:comment: "# @label has-fn-label\\n"
|
1223
|
+
- :source: |2
|
1224
|
+
- def fn_with_label
|
1225
|
+
- end
|
1226
|
+
YAML
|
1227
|
+
end
|
1228
|
+
|
1229
|
+
@@ -148,10 +276,6 @@ describe 'AppMap class Hooking', docker: false do
|
1230
|
+
:type: function
|
1231
|
+
:location: spec/fixtures/hook/instance_method.rb:8
|
1232
|
+
:static: false
|
1233
|
+
- :source: |2
|
1234
|
+
- def say_default
|
1235
|
+
- 'default'
|
1236
|
+
- end
|
1237
|
+
YAML
|
1238
|
+
end
|
1239
|
+
|
1240
|
+
@@ -746,6 +870,7 @@ describe 'AppMap class Hooking', docker: false do
|
1241
|
+
end
|
1242
|
+
secure_compare_event = YAML.load(events).find { |evt| evt[:defined_class] == 'ActiveSupport::SecurityUtils' }
|
1243
|
+
secure_compare_event.delete(:lineno)
|
1244
|
+
+ secure_compare_event.delete(:path)
|
1245
|
+
|
1246
|
+
expect(Diffy::Diff.new(<<~YAML, secure_compare_event.to_yaml).to_s).to eq('')
|
1247
|
+
---
|
1248
|
+
@@ -753,7 +878,6 @@ describe 'AppMap class Hooking', docker: false do
|
1249
|
+
:event: :call
|
1250
|
+
:defined_class: ActiveSupport::SecurityUtils
|
1251
|
+
:method_id: secure_compare
|
1252
|
+
- :path: lib/active_support/security_utils.rb
|
1253
|
+
:static: true
|
1254
|
+
:parameters:
|
1255
|
+
- :name: :a
|
1256
|
+
@@ -837,7 +961,7 @@ describe 'AppMap class Hooking', docker: false do
|
1257
|
+
entry = cm[1][:children][0][:children][0][:children][0]
|
1258
|
+
# Sanity check, make sure we got the right one
|
1259
|
+
expect(entry[:name]).to eq('secure_compare')
|
1260
|
+
- expect(entry[:labels]).to eq(%w[provider.secure_compare])
|
1261
|
+
+ expect(entry[:labels]).to eq(%w[crypto.secure_compare])
|
1262
|
+
end
|
1263
|
+
end
|
1264
|
+
|
1265
|
+
diff --git a/test/cli_test.rb b/test/cli_test.rb
|
1266
|
+
deleted file mode 100755
|
1267
|
+
index 2ea654c..0000000
|
1268
|
+
--- a/test/cli_test.rb
|
1269
|
+
+++ /dev/null
|
1270
|
+
@@ -1,116 +0,0 @@
|
1271
|
+
-#!/usr/bin/env ruby
|
1272
|
+
-# frozen_string_literal: true
|
1273
|
+
-
|
1274
|
+
-require 'test_helper'
|
1275
|
+
-require 'English'
|
1276
|
+
-
|
1277
|
+
-class CLITest < Minitest::Test
|
1278
|
+
- OUTPUT_FILENAME = File.expand_path('../tmp/appmap.json', __dir__)
|
1279
|
+
- STATS_OUTPUT_FILENAME = File.expand_path('../tmp/stats.txt', __dir__)
|
1280
|
+
-
|
1281
|
+
- def setup
|
1282
|
+
- FileUtils.rm_f OUTPUT_FILENAME
|
1283
|
+
- FileUtils.rm_f STATS_OUTPUT_FILENAME
|
1284
|
+
- end
|
1285
|
+
-
|
1286
|
+
- def test_record
|
1287
|
+
- output = Dir.chdir 'test/fixtures/cli_record_test' do
|
1288
|
+
- `#{File.expand_path '../exe/appmap', __dir__} record -o #{OUTPUT_FILENAME} ./lib/cli_record_test/main.rb`.strip
|
1289
|
+
- end
|
1290
|
+
-
|
1291
|
+
- assert_equal 0, $CHILD_STATUS.exitstatus
|
1292
|
+
- assert File.file?(OUTPUT_FILENAME), "#{OUTPUT_FILENAME} does not exist"
|
1293
|
+
- assert_equal 'Hello', output
|
1294
|
+
- output = JSON.parse(File.read(OUTPUT_FILENAME))
|
1295
|
+
- assert output['classMap'], 'Output should contain classMap'
|
1296
|
+
- assert output['events'], 'Output should contain events'
|
1297
|
+
- end
|
1298
|
+
-
|
1299
|
+
- def test_stats_to_file
|
1300
|
+
- Dir.chdir 'test/fixtures/cli_record_test' do
|
1301
|
+
- `#{File.expand_path '../exe/appmap', __dir__} record -o #{OUTPUT_FILENAME} ./lib/cli_record_test/main.rb`.strip
|
1302
|
+
- end
|
1303
|
+
- assert_equal 0, $CHILD_STATUS.exitstatus
|
1304
|
+
-
|
1305
|
+
- output = Dir.chdir 'test/fixtures/cli_record_test' do
|
1306
|
+
- `#{File.expand_path '../exe/appmap', __dir__} stats -o #{STATS_OUTPUT_FILENAME} #{OUTPUT_FILENAME}`.strip
|
1307
|
+
- end
|
1308
|
+
- assert_equal 0, $CHILD_STATUS.exitstatus
|
1309
|
+
- assert_equal '', output
|
1310
|
+
- assert File.file?(OUTPUT_FILENAME), "#{OUTPUT_FILENAME} does not exist"
|
1311
|
+
- end
|
1312
|
+
-
|
1313
|
+
-
|
1314
|
+
- def test_stats_text
|
1315
|
+
- Dir.chdir 'test/fixtures/cli_record_test' do
|
1316
|
+
- `#{File.expand_path '../exe/appmap', __dir__} record -o #{OUTPUT_FILENAME} ./lib/cli_record_test/main.rb`.strip
|
1317
|
+
- end
|
1318
|
+
- assert_equal 0, $CHILD_STATUS.exitstatus
|
1319
|
+
-
|
1320
|
+
- output = Dir.chdir 'test/fixtures/cli_record_test' do
|
1321
|
+
- `#{File.expand_path '../exe/appmap', __dir__} stats -o - #{OUTPUT_FILENAME}`.strip
|
1322
|
+
- end
|
1323
|
+
-
|
1324
|
+
- assert_equal 0, $CHILD_STATUS.exitstatus
|
1325
|
+
- assert_equal <<~OUTPUT.strip, output.strip
|
1326
|
+
- Class frequency:
|
1327
|
+
- ----------------
|
1328
|
+
- 1 Main
|
1329
|
+
-
|
1330
|
+
- Method frequency:
|
1331
|
+
- ----------------
|
1332
|
+
- 1 Main.say_hello
|
1333
|
+
- OUTPUT
|
1334
|
+
- end
|
1335
|
+
-
|
1336
|
+
- def test_stats_json
|
1337
|
+
- Dir.chdir 'test/fixtures/cli_record_test' do
|
1338
|
+
- `#{File.expand_path '../exe/appmap', __dir__} record -o #{OUTPUT_FILENAME} ./lib/cli_record_test/main.rb`.strip
|
1339
|
+
- end
|
1340
|
+
- assert_equal 0, $CHILD_STATUS.exitstatus
|
1341
|
+
-
|
1342
|
+
- output = Dir.chdir 'test/fixtures/cli_record_test' do
|
1343
|
+
- `#{File.expand_path '../exe/appmap', __dir__} stats -f json -o - #{OUTPUT_FILENAME}`.strip
|
1344
|
+
- end
|
1345
|
+
-
|
1346
|
+
- assert_equal 0, $CHILD_STATUS.exitstatus
|
1347
|
+
- assert_equal <<~OUTPUT.strip, output.strip
|
1348
|
+
- {
|
1349
|
+
- "class_frequency": [
|
1350
|
+
- {
|
1351
|
+
- "name": "Main",
|
1352
|
+
- "count": 1
|
1353
|
+
- }
|
1354
|
+
- ],
|
1355
|
+
- "method_frequency": [
|
1356
|
+
- {
|
1357
|
+
- "name": "Main.say_hello",
|
1358
|
+
- "count": 1
|
1359
|
+
- }
|
1360
|
+
- ]
|
1361
|
+
- }
|
1362
|
+
- OUTPUT
|
1363
|
+
- end
|
1364
|
+
-
|
1365
|
+
- def test_record_to_default_location
|
1366
|
+
- Dir.chdir 'test/fixtures/cli_record_test' do
|
1367
|
+
- system({ 'APPMAP_FILE' => OUTPUT_FILENAME }, "#{File.expand_path '../exe/appmap', __dir__} record ./lib/cli_record_test/main.rb")
|
1368
|
+
- end
|
1369
|
+
-
|
1370
|
+
- assert_equal 0, $CHILD_STATUS.exitstatus
|
1371
|
+
- assert File.file?(OUTPUT_FILENAME), 'appmap.json does not exist'
|
1372
|
+
- end
|
1373
|
+
-
|
1374
|
+
- def test_record_to_stdout
|
1375
|
+
- output = Dir.chdir 'test/fixtures/cli_record_test' do
|
1376
|
+
- `#{File.expand_path '../exe/appmap', __dir__} record -o - ./lib/cli_record_test/main.rb`
|
1377
|
+
- end
|
1378
|
+
-
|
1379
|
+
- assert_equal 0, $CHILD_STATUS.exitstatus
|
1380
|
+
- # Event path
|
1381
|
+
- assert_includes output, %("path":"lib/cli_record_test/main.rb")
|
1382
|
+
- # Function location
|
1383
|
+
- assert_includes output, %("location":"lib/cli_record_test/main.rb:3")
|
1384
|
+
- assert !File.file?(OUTPUT_FILENAME), "#{OUTPUT_FILENAME} should not exist"
|
1385
|
+
- end
|
1386
|
+
-end
|
1387
|
+
diff --git a/test/expectations/openssl_test_key_sign1.json b/test/expectations/openssl_test_key_sign1.json
|
1388
|
+
index 6489b47..0613a69 100644
|
1389
|
+
--- a/test/expectations/openssl_test_key_sign1.json
|
1390
|
+
+++ b/test/expectations/openssl_test_key_sign1.json
|
1391
|
+
@@ -11,8 +11,7 @@
|
1392
|
+
"name": "sign",
|
1393
|
+
"type": "function",
|
1394
|
+
"location": "lib/openssl_key_sign.rb:10",
|
1395
|
+
- "static": true,
|
1396
|
+
- "source": " def Example.sign\n key = OpenSSL::PKey::RSA.new 2048\n\n document = 'the document'\n\n digest = OpenSSL::Digest::SHA256.new\n key.sign digest, document\n end\n"
|
1397
|
+
+ "static": true
|
1398
|
+
}
|
1399
|
+
]
|
1400
|
+
}
|
1401
|
+
@@ -40,8 +39,7 @@
|
1402
|
+
"location": "OpenSSL::PKey::PKey#sign",
|
1403
|
+
"static": false,
|
1404
|
+
"labels": [
|
1405
|
+
- "security",
|
1406
|
+
- "crypto"
|
1407
|
+
+ "crypto.pkey"
|
1408
|
+
]
|
1409
|
+
}
|
1410
|
+
]
|
1411
|
+
diff --git a/test/gem_test.rb b/test/gem_test.rb
|
1412
|
+
index 6cae910..1342c2a 100644
|
1413
|
+
--- a/test/gem_test.rb
|
1414
|
+
+++ b/test/gem_test.rb
|
1415
|
+
@@ -26,7 +26,7 @@ class MinitestTest < Minitest::Test
|
1416
|
+
assert_equal 2, events.size
|
1417
|
+
assert_equal 'call', events.first['event']
|
1418
|
+
assert_equal 'default_parser', events.first['method_id']
|
1419
|
+
- assert_equal "#{Gem.loaded_specs['parser'].gem_dir}/lib/parser/base.rb", events.first['path']
|
1420
|
+
+ assert_match /\lib\/parser\/base\.rb$/, events.first['path']
|
1421
|
+
assert_equal 'return', events.second['event']
|
1422
|
+
assert_equal 1, events.second['parent_id']
|
1423
|
+
end
|
1424
|
+
diff --git a/test/rspec_test.rb b/test/rspec_test.rb
|
1425
|
+
index 72ed029..b30618b 100644
|
1426
|
+
--- a/test/rspec_test.rb
|
1427
|
+
+++ b/test/rspec_test.rb
|
1428
|
+
@@ -18,19 +18,6 @@ class RSpecTest < Minitest::Test
|
1429
|
+
end
|
1430
|
+
end
|
1431
|
+
|
1432
|
+
- def test_inventory
|
1433
|
+
- perform_test 'plain_hello_spec' do
|
1434
|
+
- appmap_file = 'tmp/appmap/rspec/Inventory.appmap.json'
|
1435
|
+
-
|
1436
|
+
- assert File.file?(appmap_file), 'appmap output file does not exist'
|
1437
|
+
- appmap = JSON.parse(File.read(appmap_file))
|
1438
|
+
- assert_equal AppMap::APPMAP_FORMAT_VERSION, appmap['version']
|
1439
|
+
- assert_includes appmap.keys, 'metadata'
|
1440
|
+
- metadata = appmap['metadata']
|
1441
|
+
- assert_equal 'Inventory', metadata['name']
|
1442
|
+
- end
|
1443
|
+
- end
|
1444
|
+
-
|
1445
|
+
def test_record_decorated_rspec
|
1446
|
+
perform_test 'decorated_hello_spec' do
|
1447
|
+
appmap_file = 'tmp/appmap/rspec/Hello_says_hello.appmap.json'
|