appmap 0.50.0 → 0.51.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce006905408a0ee15ccaee33ecb13e8bfbf1bc7b93f3cc4cbb105e2be9849bee
4
- data.tar.gz: 4f73e289a332301d6efbf0c7e8985b5e62a8a030f90c4265b221fc1d67c801be
3
+ metadata.gz: cbe39a5de48eb36889755224aa85f8d1bee3af0c75a9eb00a28dd8bdc92cc5c0
4
+ data.tar.gz: 284eae1f685e7fe5f8d897f96027683119fc26d64de03e7be108341fff8783f7
5
5
  SHA512:
6
- metadata.gz: 86108afa917712908800f9303368ee48c8b0bf7d66892e6aadd76a18d76d355106f751984579bf57f9fdd876f7e4071f75a0d6cdf4fd90dfa5ce90a93d1ed9a1
7
- data.tar.gz: b62b7793fd03c9d3cb43792b0a9b056547b4dd9f75a6b31f582a12d7a19cfdf4eaee7f566b27eaaf9a36dd4477afa396184b31bc753858b979ac5fb6804d3027
6
+ metadata.gz: a50f7cf525571c964bff72d7da580f9a38cc6eba0f31e03a592cd5cd4b1321cba70a0d3e0e8b4fb22da6bf9e4d50cbc3f5b3d346b52cc8e6749626b71197b8fa
7
+ data.tar.gz: 7850f7fabe85ea64d1e97f841a1cbd3b944005b7959cd4e75ddca0cc7788bcad7640420ee3b12003387399c332a95d0c90550caa2a0ce065d463f33f0fd416e3
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [0.51.0](https://github.com/applandinc/appmap-ruby/compare/v0.50.0...v0.51.0) (2021-06-21)
2
+
3
+
4
+ ### Features
5
+
6
+ * Provide default appmap.yml settings ([7fa8159](https://github.com/applandinc/appmap-ruby/commit/7fa8159b5020e35f13379017b44906d671e62e64))
7
+
1
8
  # [0.50.0](https://github.com/applandinc/appmap-ruby/compare/v0.49.0...v0.50.0) (2021-06-17)
2
9
 
3
10
 
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,12 +38,19 @@ 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)
53
+ def initialize_configuration(config_file_path = default_config_file_path)
47
54
  warn "Configuring AppMap from path #{config_file_path}"
48
55
  Config.load_from_file(config_file_path).tap do |configuration|
49
56
  self.configuration = configuration
@@ -118,4 +125,4 @@ if Gem.loaded_specs['minitest']
118
125
  require 'appmap/minitest'
119
126
  end
120
127
 
121
- AppMap.initialize if ENV['APPMAP'] == 'true'
128
+ AppMap.initialize_configuration if ENV['APPMAP'] == 'true'
data/lib/appmap/config.rb CHANGED
@@ -223,10 +223,14 @@ module AppMap
223
223
  'JSON::Ext::Generator::State' => TargetMethods.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json.generate])),
224
224
  }.freeze
225
225
 
226
- attr_reader :name, :packages, :exclude, :hooked_methods, :builtin_hooks
226
+ attr_reader :name, :appmap_dir, :packages, :exclude, :hooked_methods, :builtin_hooks
227
227
 
228
- def initialize(name, packages, exclude: [], functions: [])
228
+ def initialize(name,
229
+ packages: [],
230
+ exclude: [],
231
+ functions: [])
229
232
  @name = name
233
+ @appmap_dir = AppMap::DEFAULT_APPMAP_DIR
230
234
  @packages = packages
231
235
  @hook_paths = Set.new(packages.map(&:path))
232
236
  @exclude = exclude
@@ -253,38 +257,119 @@ module AppMap
253
257
  class << self
254
258
  # Loads configuration data from a file, specified by the file name.
255
259
  def load_from_file(config_file_name)
256
- require 'yaml'
257
- load YAML.safe_load(::File.read(config_file_name))
260
+ logo = lambda do
261
+ Util.color(<<~LOGO, :magenta)
262
+ ___ __ ___
263
+ / _ | ___ ___ / |/ /__ ____
264
+ / __ |/ _ \\/ _ \\/ /|_/ / _ `/ _ \\
265
+ /_/ |_/ .__/ .__/_/ /_/\\_,_/ .__/
266
+ /_/ /_/ /_/
267
+ LOGO
268
+ end
269
+
270
+ config_present = true if File.exists?(config_file_name)
271
+
272
+ config_data = if config_present
273
+ require 'yaml'
274
+ YAML.safe_load(::File.read(config_file_name))
275
+ else
276
+ warn logo.()
277
+ warn ''
278
+ warn Util.color(%Q|NOTICE: The AppMap config file #{config_file_name} was not found!|, :magenta, bold: true)
279
+ warn ''
280
+ warn Util.color(<<~MISSING_FILE_MSG, :magenta)
281
+ AppMap uses this file to customize its behavior. For example, you can use
282
+ the 'packages' setting to indicate which local file paths and dependency
283
+ gems you want to include in the AppMap. Since you haven't provided specific
284
+ settings, the appmap gem will try and guess some reasonable defaults.
285
+ To suppress this message, create the file:
286
+
287
+ #{Pathname.new(config_file_name).expand_path}.
288
+
289
+ Here are the default settings that will be used in the meantime. You can
290
+ copy and paste this example to start your appmap.yml.
291
+ MISSING_FILE_MSG
292
+ {}
293
+ end
294
+ load(config_data).tap do |config|
295
+ config_yaml = {
296
+ 'name' => config.name,
297
+ 'packages' => config.packages.select{|p| p.path}.map do |pkg|
298
+ { 'path' => pkg.path }
299
+ end,
300
+ 'exclude' => []
301
+ }.compact
302
+ unless config_present
303
+ warn Util.color(YAML.dump(config_yaml), :magenta)
304
+ warn logo.()
305
+ end
306
+ end
258
307
  end
259
308
 
260
309
  # Loads configuration from a Hash.
261
310
  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)
311
+ name = config_data['name'] || guess_name
312
+ config_params = {
313
+ exclude: config_data['exclude']
314
+ }.compact
315
+
316
+ if config_data['functions']
317
+ config_params[:functions] = config_data['functions'].map do |function_data|
318
+ package = function_data['package']
319
+ cls = function_data['class']
320
+ functions = function_data['function'] || function_data['functions']
321
+ raise %q(AppMap config 'function' element should specify 'package', 'class' and 'function' or 'functions') unless package && cls && functions
322
+
323
+ functions = Array(functions).map(&:to_sym)
324
+ labels = function_data['label'] || function_data['labels']
325
+ labels = Array(labels).map(&:to_s) if labels
326
+ Function.new(package, cls, labels, functions)
327
+ end
271
328
  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)
329
+
330
+ config_params[:packages] = \
331
+ if config_data['packages']
332
+ config_data['packages'].map do |package|
333
+ gem = package['gem']
334
+ path = package['path']
335
+ raise %q(AppMap config 'package' element should specify 'gem' or 'path', not both) if gem && path
336
+
337
+ if gem
338
+ shallow = package['shallow']
339
+ # shallow is true by default for gems
340
+ shallow = true if shallow.nil?
341
+ Package.build_from_gem(gem, exclude: package['exclude'] || [], shallow: shallow)
342
+ else
343
+ Package.build_from_path(path, exclude: package['exclude'] || [], shallow: package['shallow'])
344
+ end
345
+ end.compact
282
346
  else
283
- Package.build_from_path(path, exclude: package['exclude'] || [], shallow: package['shallow'])
347
+ Array(guess_paths).map do |path|
348
+ Package.build_from_path(path)
349
+ end
284
350
  end
285
- end.compact
286
- exclude = config_data['exclude'] || []
287
- Config.new config_data['name'], packages, exclude: exclude, functions: functions
351
+
352
+ Config.new name, config_params
353
+ end
354
+
355
+ def guess_name
356
+ reponame = lambda do
357
+ next unless File.directory?('.git')
358
+
359
+ repo_name = `git config --get remote.origin.url`.strip
360
+ repo_name.split('/').last.split('.').first unless repo_name == ''
361
+ end
362
+ dirname = -> { Dir.pwd.split('/').last }
363
+
364
+ reponame.() || dirname.()
365
+ end
366
+
367
+ def guess_paths
368
+ if defined?(::Rails)
369
+ %w[app/controllers app/models]
370
+ elsif File.directory?('lib')
371
+ %w[lib]
372
+ end
288
373
  end
289
374
  end
290
375
 
@@ -294,7 +379,7 @@ module AppMap
294
379
  packages: packages.map(&:to_h),
295
380
  functions: @functions.map(&:to_h),
296
381
  exclude: exclude
297
- }
382
+ }.compact
298
383
  end
299
384
 
300
385
  # Determines if methods defined in a file path should possibly be hooked.
data/lib/appmap/util.rb CHANGED
@@ -4,6 +4,21 @@ require 'bundler'
4
4
 
5
5
  module AppMap
6
6
  module Util
7
+ # https://wynnnetherland.com/journal/a-stylesheet-author-s-guide-to-terminal-colors/
8
+ # Embed in a String to clear all previous ANSI sequences.
9
+ CLEAR = "\e[0m"
10
+ BOLD = "\e[1m"
11
+
12
+ # Colors
13
+ BLACK = "\e[30m"
14
+ RED = "\e[31m"
15
+ GREEN = "\e[32m"
16
+ YELLOW = "\e[33m"
17
+ BLUE = "\e[34m"
18
+ MAGENTA = "\e[35m"
19
+ CYAN = "\e[36m"
20
+ WHITE = "\e[37m"
21
+
7
22
  class << self
8
23
  # scenario_filename builds a suitable file name from a scenario name.
9
24
  # Special characters are removed, and the file name is truncated to fit within
@@ -128,6 +143,12 @@ module AppMap
128
143
  FileUtils.mv tempfile.path, filename
129
144
  end
130
145
  end
146
+
147
+ def color(text, color, bold: false)
148
+ color = Util.const_get(color.to_s.upcase) if color.is_a?(Symbol)
149
+ bold = bold ? BOLD : ""
150
+ "#{bold}#{color}#{text}#{CLEAR}"
151
+ end
131
152
  end
132
153
  end
133
154
  end
@@ -3,7 +3,9 @@
3
3
  module AppMap
4
4
  URL = 'https://github.com/applandinc/appmap-ruby'
5
5
 
6
- VERSION = '0.50.0'
6
+ VERSION = '0.51.0'
7
7
 
8
8
  APPMAP_FORMAT_VERSION = '1.5.1'
9
+
10
+ DEFAULT_APPMAP_DIR = 'tmp/appmap'.freeze
9
11
  end
@@ -1,9 +1,30 @@
1
1
  require 'rails_spec_helper'
2
2
 
3
3
  describe 'Rails' do
4
+ shared_context 'rails integration test setup' do
5
+ def tmpdir
6
+ 'tmp/spec/AbstractControllerBase'
7
+ end
8
+
9
+ unless use_existing_data?
10
+ before(:all) do
11
+ FileUtils.rm_rf tmpdir
12
+ FileUtils.mkdir_p tmpdir
13
+ run_spec 'spec/controllers/users_controller_spec.rb'
14
+ run_spec 'spec/controllers/users_controller_api_spec.rb'
15
+ end
16
+ end
17
+
18
+ let(:appmap) { JSON.parse File.read File.join tmpdir, 'appmap/rspec', appmap_json_file }
19
+ let(:appmap_json_path) { File.join(tmpdir, 'appmap/rspec', appmap_json_file) }
20
+ let(:appmap) { JSON.parse File.read(appmap_json_path) }
21
+ let(:events) { appmap['events'] }
22
+ end
23
+
4
24
  %w[5 6].each do |rails_major_version| # rubocop:disable Metrics/BlockLength
5
25
  context "#{rails_major_version}" do
6
26
  include_context 'Rails app pg database', "spec/fixtures/rails#{rails_major_version}_users_app" unless use_existing_data?
27
+ include_context 'rails integration test setup'
7
28
 
8
29
  def run_spec(spec_name)
9
30
  cmd = <<~CMD.gsub "\n", ' '
@@ -13,24 +34,6 @@ describe 'Rails' do
13
34
  run_cmd cmd, chdir: fixture_dir
14
35
  end
15
36
 
16
- def tmpdir
17
- 'tmp/spec/AbstractControllerBase'
18
- end
19
-
20
- unless use_existing_data?
21
- before(:all) do
22
- FileUtils.rm_rf tmpdir
23
- FileUtils.mkdir_p tmpdir
24
- run_spec 'spec/controllers/users_controller_spec.rb'
25
- run_spec 'spec/controllers/users_controller_api_spec.rb'
26
- end
27
- end
28
-
29
- let(:appmap) { JSON.parse File.read File.join tmpdir, 'appmap/rspec', appmap_json_file }
30
- let(:appmap_json_path) { File.join(tmpdir, 'appmap/rspec', appmap_json_file) }
31
- let(:appmap) { JSON.parse File.read(appmap_json_path) }
32
- let(:events) { appmap['events'] }
33
-
34
37
  describe 'an API route' do
35
38
  describe 'creating an object' do
36
39
  let(:appmap_json_file) do
@@ -253,4 +256,40 @@ describe 'Rails' do
253
256
  end
254
257
  end
255
258
  end
259
+
260
+ describe 'with default appmap.yml' do
261
+ include_context 'Rails app pg database', "spec/fixtures/rails5_users_app" unless use_existing_data?
262
+ include_context 'rails integration test setup'
263
+
264
+ def run_spec(spec_name)
265
+ cmd = <<~CMD.gsub "\n", ' '
266
+ docker-compose run --rm -e RAILS_ENV=test -e APPMAP=true -e APPMAP_CONFIG_FILE=no/such/file
267
+ -v #{File.absolute_path tmpdir}:/app/tmp app ./bin/rspec #{spec_name}
268
+ CMD
269
+ run_cmd cmd, chdir: fixture_dir
270
+ end
271
+
272
+ let(:appmap_json_file) do
273
+ 'Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json'
274
+ end
275
+
276
+ it 'http_server_request is recorded' do
277
+ expect(events).to include(
278
+ hash_including(
279
+ 'http_server_request' => hash_including(
280
+ 'request_method' => 'POST',
281
+ 'path_info' => '/api/users'
282
+ )
283
+ )
284
+ )
285
+ end
286
+
287
+ it 'controller method is recorded' do
288
+ expect(events).to include hash_including(
289
+ 'defined_class' => 'Api::UsersController',
290
+ 'method_id' => 'build_user',
291
+ 'path' => 'app/controllers/api/users_controller.rb',
292
+ )
293
+ end
294
+ end
256
295
  end
data/spec/config_spec.rb CHANGED
@@ -55,4 +55,25 @@ describe AppMap::Config, docker: false do
55
55
 
56
56
  expect(config.to_h.deep_stringify_keys!).to eq(config_expectation)
57
57
  end
58
+
59
+ context do
60
+ let(:warnings) { @warnings ||= [] }
61
+ let(:warning) { warnings.join }
62
+ before do
63
+ expect(AppMap::Config).to receive(:warn).at_least(1) { |msg| warnings << msg }
64
+ end
65
+ it 'prints a warning and uses a default config' do
66
+ config = AppMap::Config.load_from_file 'no/such/file'
67
+ expect(config.to_h).to eq(YAML.load(<<~CONFIG))
68
+ :name: appmap-ruby
69
+ :packages:
70
+ - :path: lib
71
+ :handler_class: AppMap::Handler::Function
72
+ :shallow: false
73
+ :functions: []
74
+ :exclude: []
75
+ CONFIG
76
+ expect(warning).to include('NOTICE: The AppMap config file no/such/file was not found!')
77
+ end
78
+ end
58
79
  end
data/spec/hook_spec.rb CHANGED
@@ -21,7 +21,7 @@ describe 'AppMap class Hooking', docker: false do
21
21
  def invoke_test_file(file, setup: nil, &block)
22
22
  AppMap.configuration = nil
23
23
  package = AppMap::Config::Package.build_from_path(file)
24
- config = AppMap::Config.new('hook_spec', [ package ])
24
+ config = AppMap::Config.new('hook_spec', packages: [ package ])
25
25
  AppMap.configuration = config
26
26
  tracer = nil
27
27
  AppMap::Hook.new(config).enable do
@@ -57,7 +57,7 @@ describe 'AppMap class Hooking', docker: false do
57
57
  it 'excludes named classes and methods' do
58
58
  load 'spec/fixtures/hook/exclude.rb'
59
59
  package = AppMap::Config::Package.build_from_path('spec/fixtures/hook/exclude.rb')
60
- config = AppMap::Config.new('hook_spec', [ package ], exclude: %w[ExcludeTest])
60
+ config = AppMap::Config.new('hook_spec', packages: [ package ], exclude: %w[ExcludeTest])
61
61
  AppMap.configuration = config
62
62
 
63
63
  expect(config.never_hook?(ExcludeTest, ExcludeTest.new.method(:instance_method))).to be_truthy
@@ -62,7 +62,7 @@ describe 'Net::HTTP handler' do
62
62
  end
63
63
 
64
64
  context 'with trace enabled' do
65
- let(:configuration) { AppMap::Config.new('record_net_http_spec', []) }
65
+ let(:configuration) { AppMap::Config.new('record_net_http_spec') }
66
66
 
67
67
  after do
68
68
  AppMap.configuration = nil
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appmap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.50.0
4
+ version: 0.51.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Gilpin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-17 00:00:00.000000000 Z
11
+ date: 2021-06-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport