appmap 0.42.1 → 0.46.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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/.releaserc.yml +11 -0
  3. data/.travis.yml +33 -2
  4. data/CHANGELOG.md +44 -0
  5. data/README.md +74 -16
  6. data/README_CI.md +29 -0
  7. data/Rakefile +4 -2
  8. data/appmap.gemspec +5 -3
  9. data/lib/appmap.rb +3 -7
  10. data/lib/appmap/class_map.rb +11 -22
  11. data/lib/appmap/command/record.rb +1 -1
  12. data/lib/appmap/config.rb +180 -67
  13. data/lib/appmap/cucumber.rb +1 -1
  14. data/lib/appmap/event.rb +46 -27
  15. data/lib/appmap/handler/function.rb +19 -0
  16. data/lib/appmap/handler/net_http.rb +107 -0
  17. data/lib/appmap/handler/rails/request_handler.rb +124 -0
  18. data/lib/appmap/handler/rails/sql_handler.rb +152 -0
  19. data/lib/appmap/handler/rails/template.rb +149 -0
  20. data/lib/appmap/hook.rb +111 -70
  21. data/lib/appmap/hook/method.rb +6 -8
  22. data/lib/appmap/middleware/remote_recording.rb +1 -1
  23. data/lib/appmap/minitest.rb +22 -20
  24. data/lib/appmap/railtie.rb +5 -5
  25. data/lib/appmap/record.rb +1 -1
  26. data/lib/appmap/rspec.rb +22 -21
  27. data/lib/appmap/trace.rb +47 -6
  28. data/lib/appmap/util.rb +47 -2
  29. data/lib/appmap/version.rb +2 -2
  30. data/package-lock.json +3 -3
  31. data/release.sh +17 -0
  32. data/spec/abstract_controller_base_spec.rb +140 -34
  33. data/spec/class_map_spec.rb +5 -13
  34. data/spec/config_spec.rb +33 -1
  35. data/spec/fixtures/hook/custom_instance_method.rb +11 -0
  36. data/spec/fixtures/hook/method_named_call.rb +11 -0
  37. data/spec/fixtures/rails5_users_app/Gemfile +7 -3
  38. data/spec/fixtures/rails5_users_app/app/controllers/api/users_controller.rb +2 -0
  39. data/spec/fixtures/rails5_users_app/app/controllers/users_controller.rb +9 -1
  40. data/spec/fixtures/rails5_users_app/config/application.rb +2 -0
  41. data/spec/fixtures/rails5_users_app/create_app +8 -2
  42. data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_api_spec.rb +13 -0
  43. data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_spec.rb +2 -2
  44. data/spec/fixtures/rails5_users_app/spec/rails_helper.rb +3 -9
  45. data/spec/fixtures/rails6_users_app/Gemfile +5 -4
  46. data/spec/fixtures/rails6_users_app/app/controllers/api/users_controller.rb +1 -0
  47. data/spec/fixtures/rails6_users_app/app/controllers/users_controller.rb +9 -1
  48. data/spec/fixtures/rails6_users_app/config/application.rb +2 -0
  49. data/spec/fixtures/rails6_users_app/create_app +8 -2
  50. data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_api_spec.rb +13 -0
  51. data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_spec.rb +2 -2
  52. data/spec/fixtures/rails6_users_app/spec/rails_helper.rb +3 -9
  53. data/spec/hook_spec.rb +143 -22
  54. data/spec/record_net_http_spec.rb +160 -0
  55. data/spec/record_sql_rails_pg_spec.rb +1 -1
  56. data/spec/spec_helper.rb +16 -0
  57. data/test/expectations/openssl_test_key_sign1.json +2 -4
  58. data/test/gem_test.rb +1 -1
  59. data/test/rspec_test.rb +0 -13
  60. metadata +20 -14
  61. data/exe/appmap +0 -154
  62. data/lib/appmap/rails/request_handler.rb +0 -109
  63. data/lib/appmap/rails/sql_handler.rb +0 -150
  64. data/test/cli_test.rb +0 -116
@@ -0,0 +1,160 @@
1
+ require 'spec_helper'
2
+ require 'diffy'
3
+ require 'rack'
4
+ require 'rack/handler/webrick'
5
+
6
+ class HelloWorldApp
7
+ def call(env)
8
+ req = Rack::Request.new(env)
9
+ case req.path_info
10
+ when /hello/
11
+ [200, {"Content-Type" => "text/html"}, ["Hello World!"]]
12
+ when /goodbye/
13
+ [500, {"Content-Type" => "text/html"}, ["Goodbye Cruel World!"]]
14
+ else
15
+ [404, {"Content-Type" => "text/html"}, ["I'm Lost!"]]
16
+ end
17
+ end
18
+ end
19
+
20
+ describe 'Net::HTTP handler' do
21
+ include_context 'collect events'
22
+
23
+ def get_hello(params: nil)
24
+ http = Net::HTTP.new('localhost', 19292)
25
+ http.get [ '/hello', params ].compact.join('?')
26
+ end
27
+
28
+ before(:all) do
29
+ @rack_thread = Thread.new do
30
+ Rack::Handler::WEBrick.run HelloWorldApp.new, Port: 19292
31
+ end
32
+ 10.times do
33
+ sleep 0.1
34
+ break if get_hello.code.to_i == 200
35
+ end
36
+ raise "Web server didn't start" unless get_hello.code.to_i == 200
37
+ end
38
+
39
+ after(:all) do
40
+ @rack_thread.kill
41
+ end
42
+
43
+ def start_recording
44
+ AppMap.configuration = configuration
45
+ AppMap::Hook.new(configuration).enable
46
+
47
+ @tracer = AppMap.tracing.trace
48
+ AppMap::Event.reset_id_counter
49
+ end
50
+
51
+ def record(&block)
52
+ start_recording
53
+ begin
54
+ yield
55
+ ensure
56
+ stop_recording
57
+ end
58
+ end
59
+
60
+ def stop_recording
61
+ AppMap.tracing.delete(@tracer)
62
+ end
63
+
64
+ context 'with trace enabled' do
65
+ let(:configuration) { AppMap::Config.new('record_net_http_spec', []) }
66
+
67
+ after do
68
+ AppMap.configuration = nil
69
+ end
70
+
71
+ describe 'GET request' do
72
+ it 'with a single query parameter' do
73
+ record do
74
+ get_hello(params: 'msg=hi')
75
+ end
76
+
77
+ events = collect_events(@tracer).to_yaml
78
+ expect(Diffy::Diff.new(<<~EVENTS, events).to_s).to eq('')
79
+ ---
80
+ - :id: 1
81
+ :event: :call
82
+ :http_client_request:
83
+ :request_method: GET
84
+ :url: http://localhost:19292/hello
85
+ :headers:
86
+ Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
87
+ Accept: "*/*"
88
+ User-Agent: Ruby
89
+ Connection: close
90
+ :message:
91
+ - :name: msg
92
+ :class: String
93
+ :value: hi
94
+ - :id: 2
95
+ :event: :return
96
+ :parent_id: 1
97
+ :http_client_response:
98
+ :status_code: 200
99
+ :headers:
100
+ Content-Type: text/html
101
+ Server: WEBrick
102
+ Date: "<instanceof date>"
103
+ Content-Length: '12'
104
+ Connection: close
105
+ EVENTS
106
+ end
107
+
108
+ it 'with a multi-valued query parameter' do
109
+ record do
110
+ get_hello(params: 'ary[]=1&ary[]=2')
111
+ end
112
+
113
+ event = collect_events(@tracer).first.to_yaml
114
+ expect(Diffy::Diff.new(<<~EVENT, event).to_s).to eq('')
115
+ ---
116
+ :id: 1
117
+ :event: :call
118
+ :http_client_request:
119
+ :request_method: GET
120
+ :url: http://localhost:19292/hello
121
+ :headers:
122
+ Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
123
+ Accept: "*/*"
124
+ User-Agent: Ruby
125
+ Connection: close
126
+ :message:
127
+ - :name: ary
128
+ :class: Array
129
+ :value: '["1", "2"]'
130
+ EVENT
131
+ end
132
+
133
+ it 'with a URL encoded query parameter' do
134
+ msg = 'foo/bar?baz'
135
+ record do
136
+ get_hello(params: "msg=#{CGI.escape msg}")
137
+ end
138
+
139
+ event = collect_events(@tracer).first.to_yaml
140
+ expect(Diffy::Diff.new(<<~EVENT, event).to_s).to eq('')
141
+ ---
142
+ :id: 1
143
+ :event: :call
144
+ :http_client_request:
145
+ :request_method: GET
146
+ :url: http://localhost:19292/hello
147
+ :headers:
148
+ Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
149
+ Accept: "*/*"
150
+ User-Agent: Ruby
151
+ Connection: close
152
+ :message:
153
+ - :name: msg
154
+ :class: String
155
+ :value: #{msg}
156
+ EVENT
157
+ end
158
+ end
159
+ end
160
+ end
@@ -61,7 +61,7 @@ describe 'SQL events' do
61
61
  end
62
62
 
63
63
  context 'while listing records' do
64
- let(:test_line_number) { 23 }
64
+ let(:test_line_number) { 29 }
65
65
  let(:appmap_json) { File.join(tmpdir, 'appmap/rspec/Api_UsersController_GET_api_users_lists_the_users.appmap.json') }
66
66
 
67
67
  context 'using Sequel ORM' do
data/spec/spec_helper.rb CHANGED
@@ -14,3 +14,19 @@ require 'appmap'
14
14
  RSpec.configure do |config|
15
15
  config.example_status_persistence_file_path = "tmp/rspec_failed_examples.txt"
16
16
  end
17
+
18
+ # Re-run the Rails specs without re-generating the data. This is useful for efficiently enhancing and
19
+ # debugging the test itself.
20
+ def use_existing_data?
21
+ ENV['USE_EXISTING_DATA'] == 'true'
22
+ end
23
+
24
+ shared_context 'collect events' do
25
+ def collect_events(tracer)
26
+ [].tap do |events|
27
+ while tracer.event?
28
+ events << tracer.next_event.to_h
29
+ end
30
+ end.map(&AppMap::Util.method(:sanitize_event))
31
+ end
32
+ end
@@ -11,8 +11,7 @@
11
11
  "name": "sign",
12
12
  "type": "function",
13
13
  "location": "lib/openssl_key_sign.rb:10",
14
- "static": true,
15
- "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"
14
+ "static": true
16
15
  }
17
16
  ]
18
17
  }
@@ -40,8 +39,7 @@
40
39
  "location": "OpenSSL::PKey::PKey#sign",
41
40
  "static": false,
42
41
  "labels": [
43
- "security",
44
- "crypto"
42
+ "crypto.pkey"
45
43
  ]
46
44
  }
47
45
  ]
data/test/gem_test.rb CHANGED
@@ -26,7 +26,7 @@ class MinitestTest < Minitest::Test
26
26
  assert_equal 2, events.size
27
27
  assert_equal 'call', events.first['event']
28
28
  assert_equal 'default_parser', events.first['method_id']
29
- assert_equal "#{Gem.loaded_specs['parser'].gem_dir}/lib/parser/base.rb", events.first['path']
29
+ assert_match /\lib\/parser\/base\.rb$/, events.first['path']
30
30
  assert_equal 'return', events.second['event']
31
31
  assert_equal 1, events.second['parent_id']
32
32
  end
data/test/rspec_test.rb CHANGED
@@ -18,19 +18,6 @@ class RSpecTest < Minitest::Test
18
18
  end
19
19
  end
20
20
 
21
- def test_inventory
22
- perform_test 'plain_hello_spec' do
23
- appmap_file = 'tmp/appmap/rspec/Inventory.appmap.json'
24
-
25
- assert File.file?(appmap_file), 'appmap output file does not exist'
26
- appmap = JSON.parse(File.read(appmap_file))
27
- assert_equal AppMap::APPMAP_FORMAT_VERSION, appmap['version']
28
- assert_includes appmap.keys, 'metadata'
29
- metadata = appmap['metadata']
30
- assert_equal 'Inventory', metadata['name']
31
- end
32
- end
33
-
34
21
  def test_record_decorated_rspec
35
22
  perform_test 'decorated_hello_spec' do
36
23
  appmap_file = 'tmp/appmap/rspec/Hello_says_hello.appmap.json'
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.42.1
4
+ version: 0.46.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Gilpin
8
- autorequire:
9
- bindir: exe
8
+ autorequire:
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-29 00:00:00.000000000 Z
11
+ date: 2021-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -304,11 +304,10 @@ dependencies:
304
304
  - - ">="
305
305
  - !ruby/object:Gem::Version
306
306
  version: '0'
307
- description:
307
+ description:
308
308
  email:
309
309
  - kgilpin@gmail.com
310
- executables:
311
- - appmap
310
+ executables: []
312
311
  extensions:
313
312
  - ext/appmap/extconf.rb
314
313
  extra_rdoc_files: []
@@ -316,6 +315,7 @@ files:
316
315
  - ".dockerignore"
317
316
  - ".gitignore"
318
317
  - ".rbenv-gemsets"
318
+ - ".releaserc.yml"
319
319
  - ".rubocop.yml"
320
320
  - ".travis.yml"
321
321
  - CHANGELOG.md
@@ -324,6 +324,7 @@ files:
324
324
  - Gemfile
325
325
  - LICENSE.txt
326
326
  - README.md
327
+ - README_CI.md
327
328
  - Rakefile
328
329
  - appmap.gemspec
329
330
  - appmap.yml
@@ -334,7 +335,6 @@ files:
334
335
  - examples/mock_webapp/lib/mock_webapp/controller.rb
335
336
  - examples/mock_webapp/lib/mock_webapp/request.rb
336
337
  - examples/mock_webapp/lib/mock_webapp/user.rb
337
- - exe/appmap
338
338
  - ext/appmap/appmap.c
339
339
  - ext/appmap/extconf.rb
340
340
  - lib/appmap.rb
@@ -346,14 +346,17 @@ files:
346
346
  - lib/appmap/config.rb
347
347
  - lib/appmap/cucumber.rb
348
348
  - lib/appmap/event.rb
349
+ - lib/appmap/handler/function.rb
350
+ - lib/appmap/handler/net_http.rb
351
+ - lib/appmap/handler/rails/request_handler.rb
352
+ - lib/appmap/handler/rails/sql_handler.rb
353
+ - lib/appmap/handler/rails/template.rb
349
354
  - lib/appmap/hook.rb
350
355
  - lib/appmap/hook/method.rb
351
356
  - lib/appmap/metadata.rb
352
357
  - lib/appmap/middleware/remote_recording.rb
353
358
  - lib/appmap/minitest.rb
354
359
  - lib/appmap/open.rb
355
- - lib/appmap/rails/request_handler.rb
356
- - lib/appmap/rails/sql_handler.rb
357
360
  - lib/appmap/railtie.rb
358
361
  - lib/appmap/record.rb
359
362
  - lib/appmap/rspec.rb
@@ -379,16 +382,19 @@ files:
379
382
  - lore/public/stylesheets/style.css
380
383
  - package-lock.json
381
384
  - package.json
385
+ - release.sh
382
386
  - spec/abstract_controller_base_spec.rb
383
387
  - spec/class_map_spec.rb
384
388
  - spec/config_spec.rb
385
389
  - spec/fixtures/hook/attr_accessor.rb
386
390
  - spec/fixtures/hook/compare.rb
387
391
  - spec/fixtures/hook/constructor.rb
392
+ - spec/fixtures/hook/custom_instance_method.rb
388
393
  - spec/fixtures/hook/exception_method.rb
389
394
  - spec/fixtures/hook/exclude.rb
390
395
  - spec/fixtures/hook/instance_method.rb
391
396
  - spec/fixtures/hook/labels.rb
397
+ - spec/fixtures/hook/method_named_call.rb
392
398
  - spec/fixtures/hook/singleton_method.rb
393
399
  - spec/fixtures/rack_users_app/.dockerignore
394
400
  - spec/fixtures/rack_users_app/.gitignore
@@ -546,11 +552,11 @@ files:
546
552
  - spec/open_spec.rb
547
553
  - spec/rails_spec_helper.rb
548
554
  - spec/railtie_spec.rb
555
+ - spec/record_net_http_spec.rb
549
556
  - spec/record_sql_rails_pg_spec.rb
550
557
  - spec/remote_recording_spec.rb
551
558
  - spec/spec_helper.rb
552
559
  - spec/util_spec.rb
553
- - test/cli_test.rb
554
560
  - test/cucumber_test.rb
555
561
  - test/expectations/openssl_test_key_sign1.json
556
562
  - test/expectations/openssl_test_key_sign2.json
@@ -600,7 +606,7 @@ homepage: https://github.com/applandinc/appmap-ruby
600
606
  licenses:
601
607
  - MIT
602
608
  metadata: {}
603
- post_install_message:
609
+ post_install_message:
604
610
  rdoc_options: []
605
611
  require_paths:
606
612
  - lib
@@ -615,8 +621,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
615
621
  - !ruby/object:Gem::Version
616
622
  version: '0'
617
623
  requirements: []
618
- rubygems_version: 3.0.3
619
- signing_key:
624
+ rubygems_version: 3.0.8
625
+ signing_key:
620
626
  specification_version: 4
621
627
  summary: Record the operation of a Ruby program, using the AppLand 'AppMap' format.
622
628
  test_files: []
data/exe/appmap DELETED
@@ -1,154 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require 'gli'
5
-
6
- ENV['APPMAP_INITIALIZE'] = 'false'
7
-
8
- require 'appmap'
9
- require 'appmap/version'
10
-
11
- # AppMap CLI.
12
- module AppMap
13
- class App
14
- extend GLI::App
15
-
16
- program_desc 'AppMap client'
17
-
18
- version AppMap::VERSION
19
-
20
- subcommand_option_handling :normal
21
- arguments :strict
22
- preserve_argv true
23
-
24
- class << self
25
- protected
26
-
27
- def default_appmap_file
28
- ENV['APPMAP_FILE'] || 'appmap.json'
29
- end
30
-
31
- def output_file_flag(c, default_value: nil)
32
- c.desc 'Name of the output file'
33
- c.long_desc <<~DESC
34
- Use a single dash '-' for stdout.
35
- DESC
36
- c.default_value default_value if default_value
37
- c.arg_name 'filename'
38
- c.flag %i[o output]
39
- end
40
- end
41
-
42
- desc 'AppMap configuration file name'
43
- default_value ENV['APPMAP_CONFIG'] || 'appmap.yml'
44
- arg_name 'filename'
45
- flag %i[c config]
46
-
47
- desc 'Record the execution of a program and generate an AppMap.'
48
- arg_name 'program'
49
- command :record do |c|
50
- output_file_flag(c, default_value: default_appmap_file)
51
-
52
- c.action do |_, _, args|
53
- # My subcommand name
54
- ARGV.shift
55
-
56
- # Consume the :output option, if provided
57
- if %w[-o --output].find { |arg_name| ARGV[0] == arg_name.to_s }
58
- ARGV.shift
59
- ARGV.shift
60
- end
61
-
62
- # Name of the program to execute. GLI will ensure that it's present.
63
- program = args.shift or help_now!("'program' argument is required")
64
-
65
- # Also pop the program name from ARGV, because the command will use raw ARGV
66
- # to load the extra arguments into this Ruby process.
67
- ARGV.shift
68
-
69
- require 'appmap/command/record'
70
- AppMap::Command::Record.new(@config, program).perform do |version, metadata, class_map, events|
71
- @output_file.write JSON.generate(version: version,
72
- metadata: metadata,
73
- classMap: class_map,
74
- events: events)
75
- end
76
- end
77
- end
78
-
79
- desc 'Calculate and print statistics of scenario files.'
80
- arg_name 'filename'
81
- command :stats do |c|
82
- output_file_flag(c, default_value: '-')
83
-
84
- c.desc 'Display format for the result (text | json)'
85
- c.default_value 'text'
86
- c.flag %i[f format]
87
-
88
- c.desc 'Maximum number of lines to display for each stat'
89
- c.flag %i[l limit]
90
-
91
- c.action do |_, options, args|
92
- require 'appmap/command/stats'
93
-
94
- limit = options[:limit].to_i if options[:limit]
95
-
96
- # Name of the file to analyze. GLI will ensure that it's present.
97
- filenames = args
98
- help_now!("'filename' argument is required") if filenames.empty?
99
-
100
- require 'appmap/algorithm/stats'
101
- result = filenames.inject(::AppMap::Algorithm::Stats::Result.new([], [])) do |stats_result, filename|
102
- appmap = begin
103
- JSON.parse(File.read(filename))
104
- rescue JSON::ParserError
105
- STDERR.puts "#{filename} is not valid JSON : #{$!}"
106
- nil
107
- end
108
- stats_result.tap do
109
- if appmap
110
- limit = options[:limit].to_i if options[:limit]
111
- stats_for_file = AppMap::Command::Stats.new(appmap).perform(limit: limit)
112
- stats_result.merge!(stats_for_file)
113
- end
114
- end
115
- end
116
-
117
- result.sort!
118
- result.limit!(limit) if limit
119
-
120
- display = case options[:format]
121
- when 'json'
122
- JSON.pretty_generate(result.as_json)
123
- else
124
- result.as_text
125
- end
126
- @output_file.write display
127
- end
128
- end
129
-
130
- pre do |global, _, options, _|
131
- @config = interpret_config_option(global[:config])
132
- @output_file = interpret_output_file_option(options[:output])
133
-
134
- true
135
- end
136
-
137
- class << self
138
- protected
139
-
140
- def interpret_config_option(fname)
141
- AppMap.initialize fname
142
- end
143
-
144
- def interpret_output_file_option(file_name)
145
- Hash.new { |_, fname| -> { File.new(fname, 'w') } }.tap do |open_output_file|
146
- open_output_file[nil] = -> { nil }
147
- open_output_file['-'] = -> { $stdout }
148
- end[file_name].call
149
- end
150
- end
151
- end
152
- end
153
-
154
- exit AppMap::App.run(ARGV)