appmap 0.50.0 → 0.51.0

Sign up to get free protection for your applications and to get access to all the features.
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