appmap 0.50.0 → 0.52.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -1
  3. data/CHANGELOG.md +38 -0
  4. data/Rakefile +21 -5
  5. data/appmap.gemspec +11 -6
  6. data/exe/appmap-agent-setup +47 -0
  7. data/exe/appmap-inspect +7 -0
  8. data/lib/appmap.rb +56 -17
  9. data/lib/appmap/command/agent_setup/init.rb +44 -0
  10. data/lib/appmap/command/inspect.rb +27 -0
  11. data/lib/appmap/command_error.rb +13 -0
  12. data/lib/appmap/config.rb +96 -29
  13. data/lib/appmap/handler/rails/template.rb +19 -5
  14. data/lib/appmap/node_cli.rb +59 -0
  15. data/lib/appmap/service/guesser.rb +26 -0
  16. data/lib/appmap/trace.rb +4 -2
  17. data/lib/appmap/util.rb +52 -2
  18. data/lib/appmap/version.rb +4 -1
  19. data/package.json +6 -7
  20. data/spec/config_spec.rb +21 -0
  21. data/spec/fixtures/rails5_users_app/docker-compose.yml +1 -1
  22. data/spec/fixtures/rails6_users_app/Dockerfile +9 -0
  23. data/spec/fixtures/rails6_users_app/docker-compose.yml +1 -1
  24. data/spec/hook_spec.rb +2 -2
  25. data/spec/{abstract_controller_base_spec.rb → rails_recording_spec.rb} +39 -19
  26. data/spec/rails_spec_helper.rb +22 -0
  27. data/spec/record_net_http_spec.rb +1 -1
  28. data/test/agent_setup_cli_test.rb +37 -0
  29. data/test/fixtures/gem_test/Gemfile +1 -0
  30. data/test/inspect_cli_test.rb +12 -0
  31. data/yarn.lock +477 -0
  32. metadata +23 -47
  33. data/lib/appmap/algorithm/prune_class_map.rb +0 -67
  34. data/lib/appmap/algorithm/stats.rb +0 -91
  35. data/lib/appmap/command/record.rb +0 -38
  36. data/lib/appmap/command/stats.rb +0 -14
  37. data/lore/pages/2019-05-21-install-and-record/index.pug +0 -51
  38. data/lore/pages/2019-05-21-install-and-record/install_example_appmap.png +0 -0
  39. data/lore/pages/2019-05-21-install-and-record/metadata.yml +0 -5
  40. data/lore/pages/layout.pug +0 -66
  41. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.css +0 -1912
  42. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.css.map +0 -1
  43. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.min.css +0 -7
  44. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.min.css.map +0 -1
  45. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.css +0 -331
  46. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.css.map +0 -1
  47. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.min.css +0 -8
  48. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.min.css.map +0 -1
  49. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.css +0 -9030
  50. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.css.map +0 -1
  51. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.min.css +0 -7
  52. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.min.css.map +0 -1
  53. data/lore/public/stylesheets/style.css +0 -8
  54. data/package-lock.json +0 -1064
@@ -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
@@ -0,0 +1,59 @@
1
+ require 'open3'
2
+ require 'shellwords'
3
+ require 'appmap/util'
4
+ require 'appmap/command_error'
5
+
6
+ module AppMap
7
+ # Utilities for invoking the +@appland/appmap+ CLI.
8
+ class NodeCLI
9
+ APPMAP_JS = Pathname.new(__dir__).join('../../node_modules/@appland/cli/src/cli.js').expand_path.to_s
10
+
11
+ attr_reader :verbose
12
+ # Directory to scan for AppMaps.
13
+ attr_accessor :appmap_dir
14
+
15
+ def initialize(verbose: false, appmap_dir: AppMap::DEFAULT_APPMAP_DIR)
16
+ @verbose = verbose
17
+ @appmap_dir = appmap_dir
18
+
19
+ detect_nodejs
20
+ end
21
+
22
+ def detect_nodejs
23
+ do_fail('node', 'please install NodeJS') unless system('node --version 2>&1 > /dev/null')
24
+ true
25
+ end
26
+
27
+ def index_appmaps
28
+ command [ 'index', '--appmap-dir', appmap_dir ]
29
+ true
30
+ end
31
+
32
+ def command(command, options = {})
33
+ command.unshift << '--verbose' if verbose
34
+ command.unshift APPMAP_JS
35
+ command.unshift 'node'
36
+
37
+ warn command.join(' ') if verbose
38
+ stdout, stderr, status = Open3.capture3({ 'NODE_OPTIONS' => '--trace-warnings' }, *command.map(&:shellescape), options)
39
+ stdout_msg = stdout.split("\n").map {|line| "stdout: #{line}"}.join("\n") unless Util.blank?(stdout)
40
+ stderr_msg = stderr.split("\n").map {|line| "stderr: #{line}"}.join("\n") unless Util.blank?(stderr)
41
+ if verbose
42
+ warn stdout_msg if stdout_msg
43
+ warn stderr_msg if stderr_msg
44
+ end
45
+ unless status.exitstatus == 0
46
+ raise CommandError.new(command, [ stdout_msg, stderr_msg ].compact.join("\n"))
47
+ end
48
+ [ stdout, stderr ]
49
+ end
50
+
51
+ protected
52
+
53
+ def do_fail(command, msg)
54
+ command = command.join(' ') if command.is_a?(Array)
55
+ warn [ command, msg ].join('; ') if verbose
56
+ raise CommandError.new(command, msg)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppMap
4
+ module Service
5
+ class Guesser
6
+ POSSIBLE_PATHS = %w[app/controllers app/models lib]
7
+ class << self
8
+ def guess_name
9
+ reponame = lambda do
10
+ next unless File.directory?('.git')
11
+
12
+ repo_name = `git config --get remote.origin.url`.strip
13
+ repo_name.split('/').last.split('.').first unless repo_name == ''
14
+ end
15
+ dirname = -> { Dir.pwd.split('/').last }
16
+
17
+ reponame.() || dirname.()
18
+ end
19
+
20
+ def guess_paths
21
+ POSSIBLE_PATHS.select { |path| File.directory?(path) }
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
data/lib/appmap/trace.rb CHANGED
@@ -2,10 +2,12 @@
2
2
 
3
3
  module AppMap
4
4
  module Trace
5
- class RubyMethod
5
+ class RubyMethod < SimpleDelegator
6
6
  attr_reader :class_name, :static
7
7
 
8
8
  def initialize(package, class_name, method, static)
9
+ super(method)
10
+
9
11
  @package = package
10
12
  @class_name = class_name
11
13
  @method = method
@@ -111,7 +113,7 @@ module AppMap
111
113
  @last_package_for_thread[Thread.current.object_id] = package if package
112
114
  @events << event
113
115
  static = event.static if event.respond_to?(:static)
114
- @methods << Trace::RubyMethod.new(package, defined_class, method, static) \
116
+ record_method Trace::RubyMethod.new(package, defined_class, method, static) \
115
117
  if package && defined_class && method && (event.event == :call)
116
118
  end
117
119
 
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
@@ -86,13 +101,13 @@ module AppMap
86
101
  # Rack prepends HTTP_ to all client-sent headers.
87
102
  matching_headers = env
88
103
  .select { |k,v| k.start_with? 'HTTP_'}
89
- .reject { |k,v| v.blank? }
104
+ .reject { |k,v| blank?(v) }
90
105
  .each_with_object({}) do |kv, memo|
91
106
  key = kv[0].sub(/^HTTP_/, '').split('_').map(&:capitalize).join('-')
92
107
  value = kv[1]
93
108
  memo[key] = value
94
109
  end
95
- matching_headers.blank? ? nil : matching_headers
110
+ blank?(matching_headers) ? nil : matching_headers
96
111
  end
97
112
 
98
113
  def normalize_path(path)
@@ -128,6 +143,41 @@ 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
152
+
153
+ def classify(word)
154
+ word.split(/[\-_]/).map(&:capitalize).join
155
+ end
156
+
157
+ def deep_dup(hash)
158
+ # This is a simple way to avoid the need for deep_dup from activesupport.
159
+ Marshal.load(Marshal.dump(hash))
160
+ end
161
+
162
+ def blank?(obj)
163
+ return true if obj.nil?
164
+
165
+ return true if obj.is_a?(String) && obj == ''
166
+
167
+ return true if obj.respond_to?(:length) && obj.length == 0
168
+
169
+ return true if obj.respond_to?(:size) && obj.size == 0
170
+
171
+ false
172
+ end
173
+
174
+ def startup_message(msg)
175
+ if defined?(::Rails) && defined?(::Rails.logger) && ::Rails.logger
176
+ ::Rails.logger.debug msg
177
+ elsif ENV['DEBUG'] == 'true'
178
+ warn msg
179
+ end
180
+ end
131
181
  end
132
182
  end
133
183
  end
@@ -3,7 +3,10 @@
3
3
  module AppMap
4
4
  URL = 'https://github.com/applandinc/appmap-ruby'
5
5
 
6
- VERSION = '0.50.0'
6
+ VERSION = '0.52.0'
7
7
 
8
8
  APPMAP_FORMAT_VERSION = '1.5.1'
9
+
10
+ DEFAULT_APPMAP_DIR = 'tmp/appmap'.freeze
11
+ DEFAULT_CONFIG_FILE_PATH = 'appmap.yml'.freeze
9
12
  end
data/package.json CHANGED
@@ -1,12 +1,8 @@
1
1
  {
2
- "name": "appmap-ruby-lore",
2
+ "name": "appmap-ruby",
3
3
  "version": "1.0.0",
4
- "description": "Lore for the AppMap Rubygem",
5
- "scripts": {
6
- "start": "./node_modules/applore/bin/lore serve"
7
- },
4
+ "description": "AppMap client agent for Ruby",
8
5
  "directories": {
9
- "example": "examples",
10
6
  "lib": "lib",
11
7
  "test": "test"
12
8
  },
@@ -14,11 +10,14 @@
14
10
  "type": "git",
15
11
  "url": "git+https://github.com/applandinc/appmap-ruby.git"
16
12
  },
13
+ "author": "kgilpin@gmail.com",
14
+ "license": "MIT",
17
15
  "bugs": {
18
16
  "url": "https://github.com/applandinc/appmap-ruby/issues"
19
17
  },
20
18
  "homepage": "https://github.com/applandinc/appmap-ruby#readme",
21
19
  "dependencies": {
22
- "applore": "^0.3.0"
20
+ "@appland/cli": "^1.1.0"
23
21
  }
24
22
  }
23
+
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
@@ -13,7 +13,7 @@ services:
13
13
  build:
14
14
  context: .
15
15
  dockerfile: Dockerfile
16
- image: rails-app:${RUBY_VERSION}
16
+ image: rails5-app:${RUBY_VERSION}
17
17
  command:
18
18
  [ "./bin/rails", "server", "-b", "0.0.0.0", "webrick" ]
19
19
  environment:
@@ -4,9 +4,18 @@ ARG RUBY_VERSION
4
4
  FROM appmap:${GEM_VERSION} as appmap
5
5
 
6
6
  FROM ruby:${RUBY_VERSION}
7
+
8
+ SHELL ["/bin/bash", "-c"]
9
+
7
10
  RUN apt-get update && apt-get install -y vim less
8
11
  RUN apt-get install -y postgresql-client
9
12
 
13
+ RUN curl -fsSL https://fnm.vercel.app/install | bash \
14
+ && source /root/.bashrc \
15
+ && fnm install --lts \
16
+ && echo 'fnm default $(fnm current)' >> ~/.bashrc \
17
+ && ln -s $(which node) /usr/local/bin/node
18
+
10
19
  RUN mkdir /app
11
20
  WORKDIR /app
12
21
 
@@ -13,7 +13,7 @@ services:
13
13
  build:
14
14
  context: .
15
15
  dockerfile: Dockerfile
16
- image: rails-app:${RUBY_VERSION}
16
+ image: rails6-app:${RUBY_VERSION}
17
17
  command:
18
18
  [ "./bin/rails", "server", "-b", "0.0.0.0", "webrick" ]
19
19
  environment:
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
@@ -4,6 +4,7 @@ describe 'Rails' do
4
4
  %w[5 6].each do |rails_major_version| # rubocop:disable Metrics/BlockLength
5
5
  context "#{rails_major_version}" do
6
6
  include_context 'Rails app pg database', "spec/fixtures/rails#{rails_major_version}_users_app" unless use_existing_data?
7
+ include_context 'rails integration test setup'
7
8
 
8
9
  def run_spec(spec_name)
9
10
  cmd = <<~CMD.gsub "\n", ' '
@@ -13,24 +14,6 @@ describe 'Rails' do
13
14
  run_cmd cmd, chdir: fixture_dir
14
15
  end
15
16
 
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
17
  describe 'an API route' do
35
18
  describe 'creating an object' do
36
19
  let(:appmap_json_file) do
@@ -240,7 +223,8 @@ describe 'Rails' do
240
223
  'children' => include(hash_including(
241
224
  'name' => 'ActionView',
242
225
  'children' => include(hash_including(
243
- 'name' => 'Renderer',
226
+ # Rails 6/5 difference
227
+ 'name' => /^(Template)?Renderer$/,
244
228
  'children' => include(hash_including(
245
229
  'name' => 'render',
246
230
  'labels' => ['mvc.view']
@@ -253,4 +237,40 @@ describe 'Rails' do
253
237
  end
254
238
  end
255
239
  end
240
+
241
+ describe 'with default appmap.yml' do
242
+ include_context 'Rails app pg database', "spec/fixtures/rails5_users_app" unless use_existing_data?
243
+ include_context 'rails integration test setup'
244
+
245
+ def run_spec(spec_name)
246
+ cmd = <<~CMD.gsub "\n", ' '
247
+ docker-compose run --rm -e RAILS_ENV=test -e APPMAP=true -e APPMAP_CONFIG_FILE=no/such/file
248
+ -v #{File.absolute_path tmpdir}:/app/tmp app ./bin/rspec #{spec_name}
249
+ CMD
250
+ run_cmd cmd, chdir: fixture_dir
251
+ end
252
+
253
+ let(:appmap_json_file) do
254
+ 'Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json'
255
+ end
256
+
257
+ it 'http_server_request is recorded' do
258
+ expect(events).to include(
259
+ hash_including(
260
+ 'http_server_request' => hash_including(
261
+ 'request_method' => 'POST',
262
+ 'path_info' => '/api/users'
263
+ )
264
+ )
265
+ )
266
+ end
267
+
268
+ it 'controller method is recorded' do
269
+ expect(events).to include hash_including(
270
+ 'defined_class' => 'Api::UsersController',
271
+ 'method_id' => 'build_user',
272
+ 'path' => 'app/controllers/api/users_controller.rb',
273
+ )
274
+ end
275
+ end
256
276
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'spec_helper'
4
+ require 'active_support'
5
+ require 'active_support/core_ext'
4
6
  require 'open3'
5
7
 
6
8
  def wait_for_container(app_name)
@@ -58,3 +60,23 @@ shared_context 'Rails app pg database' do |fixture_dir|
58
60
  end
59
61
  end
60
62
  end
63
+
64
+ shared_context 'rails integration test setup' do
65
+ def tmpdir
66
+ 'tmp/spec/AbstractControllerBase'
67
+ end
68
+
69
+ unless use_existing_data?
70
+ before(:all) do
71
+ FileUtils.rm_rf tmpdir
72
+ FileUtils.mkdir_p tmpdir
73
+ run_spec 'spec/controllers/users_controller_spec.rb'
74
+ run_spec 'spec/controllers/users_controller_api_spec.rb'
75
+ end
76
+ end
77
+
78
+ let(:appmap) { JSON.parse File.read File.join tmpdir, 'appmap/rspec', appmap_json_file }
79
+ let(:appmap_json_path) { File.join(tmpdir, 'appmap/rspec', appmap_json_file) }
80
+ let(:appmap) { JSON.parse File.read(appmap_json_path) }
81
+ let(:events) { appmap['events'] }
82
+ end