appmap 0.42.0 → 0.45.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.releaserc.yml +11 -0
  3. data/.travis.yml +23 -2
  4. data/CHANGELOG.md +42 -0
  5. data/README.md +65 -6
  6. data/README_CI.md +29 -0
  7. data/Rakefile +4 -2
  8. data/appmap.gemspec +5 -3
  9. data/lib/appmap.rb +4 -7
  10. data/lib/appmap/class_map.rb +7 -10
  11. data/lib/appmap/command/record.rb +1 -1
  12. data/lib/appmap/config.rb +173 -67
  13. data/lib/appmap/cucumber.rb +1 -1
  14. data/lib/appmap/event.rb +18 -0
  15. data/lib/appmap/handler/function.rb +19 -0
  16. data/lib/appmap/handler/net_http.rb +107 -0
  17. data/lib/appmap/hook.rb +112 -56
  18. data/lib/appmap/hook/method.rb +5 -7
  19. data/lib/appmap/middleware/remote_recording.rb +1 -1
  20. data/lib/appmap/minitest.rb +22 -20
  21. data/lib/appmap/rails/request_handler.rb +30 -17
  22. data/lib/appmap/record.rb +1 -1
  23. data/lib/appmap/rspec.rb +23 -21
  24. data/lib/appmap/trace.rb +2 -1
  25. data/lib/appmap/util.rb +47 -2
  26. data/lib/appmap/version.rb +2 -2
  27. data/release.sh +17 -0
  28. data/spec/abstract_controller_base_spec.rb +77 -30
  29. data/spec/class_map_spec.rb +3 -11
  30. data/spec/config_spec.rb +33 -1
  31. data/spec/fixtures/hook/custom_instance_method.rb +11 -0
  32. data/spec/fixtures/hook/method_named_call.rb +11 -0
  33. data/spec/fixtures/rails5_users_app/Gemfile +7 -3
  34. data/spec/fixtures/rails5_users_app/app/controllers/api/users_controller.rb +2 -0
  35. data/spec/fixtures/rails5_users_app/app/controllers/users_controller.rb +9 -1
  36. data/spec/fixtures/rails5_users_app/config/application.rb +2 -0
  37. data/spec/fixtures/rails5_users_app/create_app +8 -2
  38. data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_api_spec.rb +13 -0
  39. data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_spec.rb +2 -2
  40. data/spec/fixtures/rails5_users_app/spec/rails_helper.rb +3 -9
  41. data/spec/fixtures/rails6_users_app/Gemfile +5 -4
  42. data/spec/fixtures/rails6_users_app/app/controllers/api/users_controller.rb +1 -0
  43. data/spec/fixtures/rails6_users_app/app/controllers/users_controller.rb +9 -1
  44. data/spec/fixtures/rails6_users_app/config/application.rb +2 -0
  45. data/spec/fixtures/rails6_users_app/create_app +8 -2
  46. data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_api_spec.rb +13 -0
  47. data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_spec.rb +2 -2
  48. data/spec/fixtures/rails6_users_app/spec/rails_helper.rb +3 -9
  49. data/spec/hook_spec.rb +141 -20
  50. data/spec/record_net_http_spec.rb +160 -0
  51. data/spec/record_sql_rails_pg_spec.rb +1 -1
  52. data/spec/spec_helper.rb +16 -0
  53. data/test/expectations/openssl_test_key_sign1.json +2 -4
  54. data/test/gem_test.rb +1 -1
  55. data/test/rspec_test.rb +0 -13
  56. metadata +17 -12
  57. data/exe/appmap +0 -154
  58. 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.0
4
+ version: 0.45.1
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-25 00:00:00.000000000 Z
11
+ date: 2021-05-04 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,6 +346,8 @@ 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
349
351
  - lib/appmap/hook.rb
350
352
  - lib/appmap/hook/method.rb
351
353
  - lib/appmap/metadata.rb
@@ -379,16 +381,19 @@ files:
379
381
  - lore/public/stylesheets/style.css
380
382
  - package-lock.json
381
383
  - package.json
384
+ - release.sh
382
385
  - spec/abstract_controller_base_spec.rb
383
386
  - spec/class_map_spec.rb
384
387
  - spec/config_spec.rb
385
388
  - spec/fixtures/hook/attr_accessor.rb
386
389
  - spec/fixtures/hook/compare.rb
387
390
  - spec/fixtures/hook/constructor.rb
391
+ - spec/fixtures/hook/custom_instance_method.rb
388
392
  - spec/fixtures/hook/exception_method.rb
389
393
  - spec/fixtures/hook/exclude.rb
390
394
  - spec/fixtures/hook/instance_method.rb
391
395
  - spec/fixtures/hook/labels.rb
396
+ - spec/fixtures/hook/method_named_call.rb
392
397
  - spec/fixtures/hook/singleton_method.rb
393
398
  - spec/fixtures/rack_users_app/.dockerignore
394
399
  - spec/fixtures/rack_users_app/.gitignore
@@ -546,11 +551,11 @@ files:
546
551
  - spec/open_spec.rb
547
552
  - spec/rails_spec_helper.rb
548
553
  - spec/railtie_spec.rb
554
+ - spec/record_net_http_spec.rb
549
555
  - spec/record_sql_rails_pg_spec.rb
550
556
  - spec/remote_recording_spec.rb
551
557
  - spec/spec_helper.rb
552
558
  - spec/util_spec.rb
553
- - test/cli_test.rb
554
559
  - test/cucumber_test.rb
555
560
  - test/expectations/openssl_test_key_sign1.json
556
561
  - test/expectations/openssl_test_key_sign2.json
@@ -600,7 +605,7 @@ homepage: https://github.com/applandinc/appmap-ruby
600
605
  licenses:
601
606
  - MIT
602
607
  metadata: {}
603
- post_install_message:
608
+ post_install_message:
604
609
  rdoc_options: []
605
610
  require_paths:
606
611
  - lib
@@ -615,8 +620,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
615
620
  - !ruby/object:Gem::Version
616
621
  version: '0'
617
622
  requirements: []
618
- rubygems_version: 3.0.3
619
- signing_key:
623
+ rubygems_version: 3.0.8
624
+ signing_key:
620
625
  specification_version: 4
621
626
  summary: Record the operation of a Ruby program, using the AppLand 'AppMap' format.
622
627
  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)