appmap 0.49.0 → 0.51.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/appmap.gemspec +3 -0
  4. data/exe/appmap-agent-setup +47 -0
  5. data/lib/appmap.rb +74 -7
  6. data/lib/appmap/command/init.rb +42 -0
  7. data/lib/appmap/config.rb +94 -28
  8. data/lib/appmap/handler/rails/template.rb +19 -5
  9. data/lib/appmap/minitest.rb +8 -2
  10. data/lib/appmap/railtie.rb +7 -0
  11. data/lib/appmap/rspec.rb +8 -2
  12. data/lib/appmap/service/guesser.rb +26 -0
  13. data/lib/appmap/trace.rb +4 -2
  14. data/lib/appmap/util.rb +21 -0
  15. data/lib/appmap/version.rb +4 -1
  16. data/spec/abstract_controller_base_spec.rb +57 -18
  17. data/spec/config_spec.rb +21 -0
  18. data/spec/fixtures/rails5_users_app/config/application.rb +0 -8
  19. data/spec/fixtures/rails5_users_app/spec/rails_helper.rb +0 -2
  20. data/spec/fixtures/rails6_users_app/config/application.rb +0 -8
  21. data/spec/fixtures/rails6_users_app/spec/rails_helper.rb +0 -2
  22. data/spec/hook_spec.rb +2 -2
  23. data/spec/record_net_http_spec.rb +1 -1
  24. data/test/cli_test.rb +37 -0
  25. metadata +9 -29
  26. data/lib/appmap/algorithm/prune_class_map.rb +0 -67
  27. data/lib/appmap/algorithm/stats.rb +0 -91
  28. data/lib/appmap/command/record.rb +0 -38
  29. data/lib/appmap/command/stats.rb +0 -14
  30. data/lore/pages/2019-05-21-install-and-record/index.pug +0 -51
  31. data/lore/pages/2019-05-21-install-and-record/install_example_appmap.png +0 -0
  32. data/lore/pages/2019-05-21-install-and-record/metadata.yml +0 -5
  33. data/lore/pages/layout.pug +0 -66
  34. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.css +0 -1912
  35. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.css.map +0 -1
  36. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.min.css +0 -7
  37. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.min.css.map +0 -1
  38. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.css +0 -331
  39. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.css.map +0 -1
  40. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.min.css +0 -8
  41. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.min.css.map +0 -1
  42. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.css +0 -9030
  43. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.css.map +0 -1
  44. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.min.css +0 -7
  45. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.min.css.map +0 -1
  46. data/lore/public/stylesheets/style.css +0 -8
  47. data/package-lock.json +0 -1064
  48. data/package.json +0 -24
  49. data/spec/fixtures/rails5_users_app/config/initializers/record_button.rb +0 -3
  50. data/spec/fixtures/rails6_users_app/config/initializers/record_button.rb +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '02814a0f9d0927d19c2d341a22d8f8ec66b83bc022833a09a7777ce81ec4edfc'
4
- data.tar.gz: 11b2786d88e360fc78e046675ae799e7106625dd02b4a3070c3835bfe8feea05
3
+ metadata.gz: 5de4c6db2c2be91ba2d0c844ef4fbed14ea1b65b10abde9ef99ccbe50cd96639
4
+ data.tar.gz: 7b51407f7b49e3aa492b85fa439a55f593e0a2b772796bba0ccbd18bb4188149
5
5
  SHA512:
6
- metadata.gz: c852ae464d52f9f29ef9222fa1627f99b4f5baf1907b1cd54cbfb969ff2323cb19e0d5b53bad1356518ec21a07243ea71237ad831a62ffe825a2601cca1a5cf7
7
- data.tar.gz: 49c5ba0d0c20d8bee6e4c768544ab6abe0f0f0d71a1a59bc04fcf867e93bc743eda4cc94f7505c0a1194c07e95e2b8e7f875a42b16aa3da2a21068a61ba42a10
6
+ metadata.gz: 13662be867000e4239ed4f8587d0108d0af04b63d7727d51c3f9fa16b7284e233a590ffad301375d97d6155289c17c267eb0b8003899b8489f5afa11fd296d70
7
+ data.tar.gz: 228adedfd313b54d6b6b4f03c143d94366c8815d0daac8793d70f2e8da909712504f371f5c13f43a22d647828e4d8bb83f1dfb5c023149f8380816b8df0a8819
data/CHANGELOG.md CHANGED
@@ -1,3 +1,48 @@
1
+ ## [0.51.3](https://github.com/applandinc/appmap-ruby/compare/v0.51.2...v0.51.3) (2021-06-22)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Remove outdate lore, command, and algorithm code ([d899989](https://github.com/applandinc/appmap-ruby/commit/d8999896c611c16f51a092f5f7afb3d7203d7e72))
7
+
8
+ ## [0.51.2](https://github.com/applandinc/appmap-ruby/compare/v0.51.1...v0.51.2) (2021-06-22)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Be less verbose when logging config messages ([fba2fd0](https://github.com/applandinc/appmap-ruby/commit/fba2fd01dbb7b1830194b49285654d6657d1c786))
14
+ * Method objects must support eql? and hash to ensure they are unique in a Set ([f4d5b11](https://github.com/applandinc/appmap-ruby/commit/f4d5b11db90aa50bdd1f768e039927833e83c30f))
15
+ * Require rails, then appmap/railtie ([07967a1](https://github.com/applandinc/appmap-ruby/commit/07967a14609891544a7dd874c648b7ef5a505f21))
16
+ * Use a hybrid strategy to auto-requiring appmap modules ([6fb09b8](https://github.com/applandinc/appmap-ruby/commit/6fb09b8c0bd55b1e29967d459ce1e2bd5b1ba9fe))
17
+
18
+ ## [0.51.1](https://github.com/applandinc/appmap-ruby/compare/v0.51.0...v0.51.1) (2021-06-21)
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+ * Add missing require 'yaml' ([1187a02](https://github.com/applandinc/appmap-ruby/commit/1187a023243caaab8cd48de5cbbddefa361636ad))
24
+
25
+ # [0.51.0](https://github.com/applandinc/appmap-ruby/compare/v0.50.0...v0.51.0) (2021-06-21)
26
+
27
+
28
+ ### Features
29
+
30
+ * Provide default appmap.yml settings ([7fa8159](https://github.com/applandinc/appmap-ruby/commit/7fa8159b5020e35f13379017b44906d671e62e64))
31
+
32
+ # [0.50.0](https://github.com/applandinc/appmap-ruby/compare/v0.49.0...v0.50.0) (2021-06-17)
33
+
34
+
35
+ ### Bug Fixes
36
+
37
+ * Remove appmap configuration in test cases which now occurs automatically ([7391c4c](https://github.com/applandinc/appmap-ruby/commit/7391c4c36ed80f98a6b82ccd43f05de488e7cd2f))
38
+
39
+
40
+ ### Features
41
+
42
+ * Direct minitest and rspec startup messages to the Rails log, when available ([15f6444](https://github.com/applandinc/appmap-ruby/commit/15f6444b0fad3ce7d9e91273b6a1116e470c2a89))
43
+ * Enroll railtie, rspec, and minitest helpers automatically ([1709374](https://github.com/applandinc/appmap-ruby/commit/1709374ee7b5183482c55cf4c7386266fa517262))
44
+ * railtie enrolls the app in remote recording ([3a1f8aa](https://github.com/applandinc/appmap-ruby/commit/3a1f8aac1d83c4df04b5da55ed33d418235e348b))
45
+
1
46
  # [0.49.0](https://github.com/applandinc/appmap-ruby/compare/v0.48.2...v0.49.0) (2021-06-16)
2
47
 
3
48
 
data/appmap.gemspec CHANGED
@@ -22,6 +22,9 @@ Gem::Specification.new do |spec|
22
22
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
23
  spec.files = `git ls-files --no-deleted`.split("
24
24
  ")
25
+ spec.bindir = 'exe'
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+
25
28
  spec.extensions << "ext/appmap/extconf.rb"
26
29
 
27
30
  spec.require_paths = ['lib']
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'gli'
5
+
6
+ require 'appmap'
7
+
8
+ class App
9
+ extend GLI::App
10
+
11
+ program_desc 'CLI tool to be used by IDEs for AppMap setup and configuration'
12
+
13
+ version AppMap::VERSION
14
+
15
+ subcommand_option_handling :normal
16
+ arguments :strict
17
+ preserve_argv true
18
+
19
+ desc 'AppMap configuration file name'
20
+ default_value ENV['APPMAP_CONFIG'] || AppMap::DEFAULT_CONFIG_FILE_PATH
21
+ arg_name 'filename'
22
+ flag %i[c config]
23
+
24
+ desc 'Creates base configuration file for the current project'
25
+ command :init do |c|
26
+ c.action do
27
+ require 'appmap/command/init'
28
+ AppMap::Command::Init.new(@config_file).perform
29
+ end
30
+ end
31
+
32
+ pre do |global, _, _, _|
33
+ @config_file = global[:config]
34
+ @config = interpret_config_option(@config_file) if File.exist?(@config_file)
35
+ true
36
+ end
37
+
38
+ class << self
39
+ protected
40
+
41
+ def interpret_config_option(fname)
42
+ AppMap::Config.load_from_file fname
43
+ end
44
+ end
45
+ end
46
+
47
+ exit App.run(ARGV)
data/lib/appmap.rb CHANGED
@@ -27,7 +27,7 @@ module AppMap
27
27
  # Gets the configuration. If there is no configuration, the default
28
28
  # configuration is initialized.
29
29
  def configuration
30
- @configuration ||= initialize
30
+ @configuration ||= initialize_configuration
31
31
  end
32
32
 
33
33
  # Sets the configuration. This is only expected to happen once per
@@ -38,19 +38,34 @@ module AppMap
38
38
  @configuration = config
39
39
  end
40
40
 
41
- # Configures AppMap for recording. Default behavior is to configure from "appmap.yml".
41
+ def default_config_file_path
42
+ ENV['APPMAP_CONFIG_FILE'] || 'appmap.yml'
43
+ end
44
+
45
+ # Configures AppMap for recording. Default behavior is to configure from
46
+ # APPMAP_CONFIG_FILE, or 'appmap.yml'. If no config file is available, a
47
+ # configuration will be automatically generated and used - and the user is prompted
48
+ # to create the config file.
49
+ #
42
50
  # This method also activates the code hooks which record function calls as trace events.
43
51
  # Call this function before the program code is loaded by the Ruby VM, otherwise
44
52
  # the load events won't be seen and the hooks won't activate.
45
- def initialize(config_file_path = 'appmap.yml')
46
- raise "AppMap configuration file #{config_file_path} does not exist" unless ::File.exists?(config_file_path)
47
- warn "Configuring AppMap from path #{config_file_path}"
53
+ def initialize_configuration(config_file_path = default_config_file_path)
54
+ startup_message "Configuring AppMap from path #{config_file_path}"
48
55
  Config.load_from_file(config_file_path).tap do |configuration|
49
56
  self.configuration = configuration
50
57
  Hook.new(configuration).enable
51
58
  end
52
59
  end
53
60
 
61
+ def info(msg)
62
+ if defined?(::Rails) && defined?(::Rails.logger)
63
+ ::Rails.logger.info msg
64
+ else
65
+ warn msg
66
+ end
67
+ end
68
+
54
69
  # Used to start tracing, stop tracing, and record events.
55
70
  def tracing
56
71
  @tracing ||= Trace::Tracing.new
@@ -94,8 +109,60 @@ module AppMap
94
109
  @metadata ||= Metadata.detect.freeze
95
110
  @metadata.deep_dup
96
111
  end
112
+
113
+ def startup_message(msg)
114
+ if defined?(::Rails) && defined?(::Rails.logger) && ::Rails.logger
115
+ ::Rails.logger.debug msg
116
+ elsif ENV['DEBUG'] == 'true'
117
+ warn msg
118
+ end
119
+ end
97
120
  end
98
121
  end
99
122
 
100
- require 'appmap/railtie' if defined?(::Rails::Railtie)
101
- AppMap.initialize if ENV['APPMAP'] == 'true'
123
+ lambda do
124
+ Initializer = Struct.new(:class_name, :module_name, :gem_module_name)
125
+
126
+ INITIALIZERS = {
127
+ 'Rails::Railtie' => Initializer.new('AppMap::Railtie', 'appmap/railtie', 'railtie'),
128
+ 'RSpec' => Initializer.new('AppMap::RSpec', 'appmap/rspec', 'rspec-core'),
129
+ 'Minitest::Unit::TestCase' => Initializer.new('AppMap::Minitest', 'appmap/minitest', 'minitest')
130
+ }
131
+
132
+ TracePoint.new(:class) do |tp|
133
+ cls_name = tp.self.name
134
+ initializers = INITIALIZERS.delete(cls_name)
135
+ if initializers
136
+ initializers = [ initializers ] unless initializers.is_a?(Array)
137
+ next if Object.const_defined?(initializers.first.class_name)
138
+
139
+ gem_module_name = initializers.first.gem_module_name
140
+
141
+ AppMap.startup_message AppMap::Util.color(<<~LOAD_MSG, :magenta)
142
+ When 'appmap' was loaded, '#{gem_module_name}' had not been loaded yet. Now '#{gem_module_name}' has
143
+ just been loaded, so the following AppMap modules will be automatically required:
144
+
145
+ #{initializers.map(&:module_name).join("\n")}
146
+
147
+ To suppress this message, ensure '#{gem_module_name}' appears before 'appmap' in your Gemfile.
148
+ LOAD_MSG
149
+ initializers.each do |init|
150
+ require init.module_name
151
+ end
152
+ end
153
+ end.enable
154
+
155
+ if defined?(::Rails::Railtie)
156
+ require 'appmap/railtie'
157
+ end
158
+
159
+ if defined?(::RSpec)
160
+ require 'appmap/rspec'
161
+ end
162
+
163
+ if defined?(::Minitest)
164
+ require 'appmap/minitest'
165
+ end
166
+ end.call
167
+
168
+ AppMap.initialize_configuration if ENV['APPMAP'] == 'true'
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'appmap/service/guesser'
5
+ require 'appmap/util'
6
+
7
+ module AppMap
8
+ module Command
9
+ InitStruct = Struct.new(:config_file)
10
+
11
+ class Init < InitStruct
12
+ def perform
13
+ if File.exist?(config_file)
14
+ puts AppMap::Util.color(%(The AppMap config file #{config_file} already exists.), :magenta)
15
+ return
16
+ end
17
+
18
+ ensure_directory_exists
19
+
20
+ config = {
21
+ 'name' => Service::Guesser.guess_name,
22
+ 'packages' => Service::Guesser.guess_paths.map { |path| { 'path' => path } }
23
+ }
24
+ content = YAML.dump(config).gsub("---\n", '')
25
+
26
+ File.write(config_file, content)
27
+ puts AppMap::Util.color(
28
+ %(The following AppMap config file #{config_file} has been created:),
29
+ :green
30
+ )
31
+ puts content
32
+ end
33
+
34
+ private
35
+
36
+ def ensure_directory_exists
37
+ dirname = File.dirname(config_file)
38
+ FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
39
+ end
40
+ end
41
+ end
42
+ end
data/lib/appmap/config.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'yaml'
3
4
  require 'appmap/handler/net_http'
4
5
  require 'appmap/handler/rails/template'
6
+ require 'appmap/service/guesser'
5
7
 
6
8
  module AppMap
7
9
  class Config
@@ -223,10 +225,14 @@ module AppMap
223
225
  'JSON::Ext::Generator::State' => TargetMethods.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json.generate])),
224
226
  }.freeze
225
227
 
226
- attr_reader :name, :packages, :exclude, :hooked_methods, :builtin_hooks
228
+ attr_reader :name, :appmap_dir, :packages, :exclude, :hooked_methods, :builtin_hooks
227
229
 
228
- def initialize(name, packages, exclude: [], functions: [])
230
+ def initialize(name,
231
+ packages: [],
232
+ exclude: [],
233
+ functions: [])
229
234
  @name = name
235
+ @appmap_dir = AppMap::DEFAULT_APPMAP_DIR
230
236
  @packages = packages
231
237
  @hook_paths = Set.new(packages.map(&:path))
232
238
  @exclude = exclude
@@ -253,38 +259,98 @@ module AppMap
253
259
  class << self
254
260
  # Loads configuration data from a file, specified by the file name.
255
261
  def load_from_file(config_file_name)
256
- require 'yaml'
257
- load YAML.safe_load(::File.read(config_file_name))
262
+ logo = lambda do
263
+ Util.color(<<~LOGO, :magenta)
264
+ ___ __ ___
265
+ / _ | ___ ___ / |/ /__ ____
266
+ / __ |/ _ \\/ _ \\/ /|_/ / _ `/ _ \\
267
+ /_/ |_/ .__/ .__/_/ /_/\\_,_/ .__/
268
+ /_/ /_/ /_/
269
+ LOGO
270
+ end
271
+
272
+ config_present = true if File.exists?(config_file_name)
273
+
274
+ config_data = if config_present
275
+ YAML.safe_load(::File.read(config_file_name))
276
+ else
277
+ warn logo.()
278
+ warn ''
279
+ warn Util.color(%Q|NOTICE: The AppMap config file #{config_file_name} was not found!|, :magenta, bold: true)
280
+ warn ''
281
+ warn Util.color(<<~MISSING_FILE_MSG, :magenta)
282
+ AppMap uses this file to customize its behavior. For example, you can use
283
+ the 'packages' setting to indicate which local file paths and dependency
284
+ gems you want to include in the AppMap. Since you haven't provided specific
285
+ settings, the appmap gem will try and guess some reasonable defaults.
286
+ To suppress this message, create the file:
287
+
288
+ #{Pathname.new(config_file_name).expand_path}
289
+
290
+ Here are the default settings that will be used in the meantime. You can
291
+ copy and paste this example to start your appmap.yml.
292
+ MISSING_FILE_MSG
293
+ {}
294
+ end
295
+ load(config_data).tap do |config|
296
+ config_yaml = {
297
+ 'name' => config.name,
298
+ 'packages' => config.packages.select{|p| p.path}.map do |pkg|
299
+ { 'path' => pkg.path }
300
+ end,
301
+ 'exclude' => []
302
+ }.compact
303
+ unless config_present
304
+ warn Util.color(YAML.dump(config_yaml), :magenta)
305
+ warn logo.()
306
+ end
307
+ end
258
308
  end
259
309
 
260
310
  # Loads configuration from a Hash.
261
311
  def load(config_data)
262
- functions = (config_data['functions'] || []).map do |function_data|
263
- package = function_data['package']
264
- cls = function_data['class']
265
- functions = function_data['function'] || function_data['functions']
266
- raise 'AppMap class configuration should specify package, class and function(s)' unless package && cls && functions
267
- functions = Array(functions).map(&:to_sym)
268
- labels = function_data['label'] || function_data['labels']
269
- labels = Array(labels).map(&:to_s) if labels
270
- Function.new(package, cls, labels, functions)
312
+ name = config_data['name'] || Service::Guesser.guess_name
313
+ config_params = {
314
+ exclude: config_data['exclude']
315
+ }.compact
316
+
317
+ if config_data['functions']
318
+ config_params[:functions] = config_data['functions'].map do |function_data|
319
+ package = function_data['package']
320
+ cls = function_data['class']
321
+ functions = function_data['function'] || function_data['functions']
322
+ raise %q(AppMap config 'function' element should specify 'package', 'class' and 'function' or 'functions') unless package && cls && functions
323
+
324
+ functions = Array(functions).map(&:to_sym)
325
+ labels = function_data['label'] || function_data['labels']
326
+ labels = Array(labels).map(&:to_s) if labels
327
+ Function.new(package, cls, labels, functions)
328
+ end
271
329
  end
272
- packages = (config_data['packages'] || []).map do |package|
273
- gem = package['gem']
274
- path = package['path']
275
- raise 'AppMap package configuration should specify gem or path, not both' if gem && path
276
-
277
- if gem
278
- shallow = package['shallow']
279
- # shallow is true by default for gems
280
- shallow = true if shallow.nil?
281
- Package.build_from_gem(gem, exclude: package['exclude'] || [], shallow: shallow)
330
+
331
+ config_params[:packages] = \
332
+ if config_data['packages']
333
+ config_data['packages'].map do |package|
334
+ gem = package['gem']
335
+ path = package['path']
336
+ raise %q(AppMap config 'package' element should specify 'gem' or 'path', not both) if gem && path
337
+
338
+ if gem
339
+ shallow = package['shallow']
340
+ # shallow is true by default for gems
341
+ shallow = true if shallow.nil?
342
+ Package.build_from_gem(gem, exclude: package['exclude'] || [], shallow: shallow)
343
+ else
344
+ Package.build_from_path(path, exclude: package['exclude'] || [], shallow: package['shallow'])
345
+ end
346
+ end.compact
282
347
  else
283
- Package.build_from_path(path, exclude: package['exclude'] || [], shallow: package['shallow'])
348
+ Array(Service::Guesser.guess_paths).map do |path|
349
+ Package.build_from_path(path)
350
+ end
284
351
  end
285
- end.compact
286
- exclude = config_data['exclude'] || []
287
- Config.new config_data['name'], packages, exclude: exclude, functions: functions
352
+
353
+ Config.new name, config_params
288
354
  end
289
355
  end
290
356
 
@@ -294,7 +360,7 @@ module AppMap
294
360
  packages: packages.map(&:to_h),
295
361
  functions: @functions.map(&:to_h),
296
362
  exclude: exclude
297
- }
363
+ }.compact
298
364
  end
299
365
 
300
366
  # Determines if methods defined in a file path should possibly be hooked.
@@ -14,16 +14,30 @@ module AppMap
14
14
  # The class name is generated from the template path. The package name is
15
15
  # 'app/views', and the method name is 'render'. The source location of the method
16
16
  # is, of course, the path to the view template.
17
- TemplateMethod = Struct.new(:path) do
18
- private_instance_methods :path
17
+ class TemplateMethod
19
18
  attr_reader :class_name
20
-
19
+
20
+ attr_reader :path
21
+ private_instance_methods :path
22
+
21
23
  def initialize(path)
22
- super
24
+ @path = path
23
25
 
24
26
  @class_name = path.parameterize.underscore
25
27
  end
26
-
28
+
29
+ def id
30
+ [ package, path, name ]
31
+ end
32
+
33
+ def hash
34
+ id.hash
35
+ end
36
+
37
+ def eql?(other)
38
+ other.is_a?(TemplateMethod) && id.eql?(other.id)
39
+ end
40
+
27
41
  def package
28
42
  'app/views'
29
43
  end