appmap 0.25.2 → 0.28.1

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -2
  3. data/CHANGELOG.md +30 -0
  4. data/README.md +123 -39
  5. data/exe/appmap +3 -57
  6. data/lib/appmap.rb +51 -32
  7. data/lib/appmap/algorithm/stats.rb +2 -1
  8. data/lib/appmap/class_map.rb +1 -1
  9. data/lib/appmap/command/record.rb +2 -61
  10. data/lib/appmap/cucumber.rb +89 -0
  11. data/lib/appmap/event.rb +6 -6
  12. data/lib/appmap/hook.rb +27 -13
  13. data/lib/appmap/metadata.rb +62 -0
  14. data/lib/appmap/middleware/remote_recording.rb +2 -7
  15. data/lib/appmap/rails/action_handler.rb +2 -2
  16. data/lib/appmap/rails/sql_handler.rb +2 -2
  17. data/lib/appmap/railtie.rb +2 -2
  18. data/lib/appmap/rspec.rb +20 -38
  19. data/lib/appmap/trace.rb +11 -11
  20. data/lib/appmap/util.rb +40 -0
  21. data/lib/appmap/version.rb +1 -1
  22. data/package-lock.json +3 -3
  23. data/spec/abstract_controller4_base_spec.rb +1 -1
  24. data/spec/abstract_controller_base_spec.rb +1 -1
  25. data/spec/fixtures/hook/singleton_method.rb +54 -0
  26. data/spec/fixtures/rails_users_app/Gemfile +1 -0
  27. data/spec/fixtures/rails_users_app/features/api_users.feature +13 -0
  28. data/spec/fixtures/rails_users_app/features/support/env.rb +4 -0
  29. data/spec/fixtures/rails_users_app/features/support/hooks.rb +11 -0
  30. data/spec/fixtures/rails_users_app/features/support/steps.rb +18 -0
  31. data/spec/hook_spec.rb +107 -23
  32. data/spec/rails_spec_helper.rb +2 -0
  33. data/spec/rspec_feature_metadata_spec.rb +2 -0
  34. data/spec/spec_helper.rb +4 -0
  35. data/spec/util_spec.rb +21 -0
  36. data/test/cli_test.rb +2 -15
  37. data/test/cucumber_test.rb +72 -0
  38. data/test/fixtures/cucumber4_recorder/Gemfile +5 -0
  39. data/test/fixtures/cucumber4_recorder/appmap.yml +3 -0
  40. data/test/fixtures/cucumber4_recorder/features/say_hello.feature +5 -0
  41. data/test/fixtures/cucumber4_recorder/features/support/env.rb +5 -0
  42. data/test/fixtures/cucumber4_recorder/features/support/hooks.rb +11 -0
  43. data/test/fixtures/cucumber4_recorder/features/support/steps.rb +9 -0
  44. data/test/fixtures/cucumber4_recorder/lib/hello.rb +7 -0
  45. data/test/fixtures/cucumber_recorder/Gemfile +5 -0
  46. data/test/fixtures/cucumber_recorder/appmap.yml +3 -0
  47. data/test/fixtures/cucumber_recorder/features/say_hello.feature +5 -0
  48. data/test/fixtures/cucumber_recorder/features/support/env.rb +5 -0
  49. data/test/fixtures/cucumber_recorder/features/support/hooks.rb +11 -0
  50. data/test/fixtures/cucumber_recorder/features/support/steps.rb +9 -0
  51. data/test/fixtures/cucumber_recorder/lib/hello.rb +7 -0
  52. data/test/fixtures/rspec_recorder/Gemfile +1 -1
  53. data/test/fixtures/rspec_recorder/spec/decorated_hello_spec.rb +12 -0
  54. data/test/rspec_test.rb +5 -0
  55. metadata +26 -4
  56. data/lib/appmap/command/upload.rb +0 -101
  57. data/spec/fixtures/hook/class_method.rb +0 -17
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'open3'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rails_spec_helper'
2
4
 
3
5
  describe 'RSpec feature and feature group metadata' do
@@ -4,6 +4,10 @@ require 'json'
4
4
  require 'yaml'
5
5
  require 'English'
6
6
  require 'webdrivers/chromedriver'
7
+
8
+ # Disable default initialization of AppMap
9
+ ENV['APPMAP_INITIALIZE'] = 'false'
10
+
7
11
  require 'appmap'
8
12
 
9
13
  RSpec.configure do |config|
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'appmap/util'
5
+
6
+ describe AppMap::Util do
7
+ let(:subject) { AppMap::Util.method(:scenario_filename) }
8
+ describe 'scenario_filename' do
9
+ it 'leaves short names alone' do
10
+ expect(subject.call('foobar')).to eq('foobar.appmap.json')
11
+ end
12
+ it 'has a customizable suffix' do
13
+ expect(subject.call('foobar', extension: '.json')).to eq('foobar.json')
14
+ end
15
+ it 'limits the filename length' do
16
+ fname = (0...104).map { |i| ((i % 26) + 97).chr }.join
17
+
18
+ expect(subject.call(fname, max_length: 50)).to eq('abcdefghijklmno-RAd_SFbH1sUZ_OXfwPsfzw.appmap.json')
19
+ end
20
+ end
21
+ end
@@ -59,7 +59,7 @@ class CLITest < Minitest::Test
59
59
 
60
60
  Method frequency:
61
61
  ----------------
62
- 2 Main.say_hello
62
+ 1 Main.say_hello
63
63
  OUTPUT
64
64
  end
65
65
 
@@ -85,7 +85,7 @@ class CLITest < Minitest::Test
85
85
  "method_frequency": [
86
86
  {
87
87
  "name": "Main.say_hello",
88
- "count": 2
88
+ "count": 1
89
89
  }
90
90
  ]
91
91
  }
@@ -113,17 +113,4 @@ class CLITest < Minitest::Test
113
113
  assert_includes output, %("location":"lib/cli_record_test/main.rb:3")
114
114
  assert !File.file?(OUTPUT_FILENAME), "#{OUTPUT_FILENAME} should not exist"
115
115
  end
116
-
117
- def test_upload
118
- Dir.chdir 'test/fixtures/cli_record_test' do
119
- `#{File.expand_path '../exe/appmap', __dir__} record -o #{OUTPUT_FILENAME} ./lib/cli_record_test/main.rb`
120
- end
121
-
122
- upload_output = `./exe/appmap upload --org default --user admin --no-open #{OUTPUT_FILENAME}`
123
- assert_equal 0, $CHILD_STATUS.exitstatus
124
- # Example: 93e1e07d-4b39-49ac-82bf-27d63e296cae
125
- assert_match(/Scenario Id/, upload_output)
126
- assert_match(/Batch Id/, upload_output)
127
- assert_match(/[0-9a-f]+\-[0-9a-f\-]+/, upload_output)
128
- end
129
116
  end
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'test_helper'
5
+ require 'English'
6
+
7
+ class CucumberTest < Minitest::Test
8
+ def perform_test(dir)
9
+ Bundler.with_clean_env do
10
+ Dir.chdir "test/fixtures/#{dir}" do
11
+ FileUtils.rm_rf 'tmp'
12
+ system 'bundle config --local local.appmap ../../..'
13
+ system 'bundle'
14
+ system({ 'APPMAP' => 'true' }, %(bundle exec cucumber))
15
+
16
+ yield
17
+ end
18
+ end
19
+ end
20
+
21
+ def test_cucumber
22
+ perform_test 'cucumber_recorder' do
23
+ appmap_file = 'tmp/appmap/cucumber/Say_hello.appmap.json'
24
+
25
+ assert File.file?(appmap_file),
26
+ %(appmap output file does not exist in #{Dir.new('tmp/appmap/cucumber').entries.join(', ')})
27
+ appmap = JSON.parse(File.read(appmap_file))
28
+ assert_equal AppMap::APPMAP_FORMAT_VERSION, appmap['version']
29
+ assert_includes appmap.keys, 'metadata'
30
+ metadata = appmap['metadata']
31
+
32
+ assert_equal 'say_hello', metadata['feature_group']
33
+ assert_equal 'I can say hello', metadata['feature']
34
+ assert_equal 'Say hello', metadata['name']
35
+ assert_includes metadata.keys, 'client'
36
+ assert_equal({ name: 'appmap', url: AppMap::URL, version: AppMap::VERSION }.stringify_keys, metadata['client'])
37
+ assert_includes metadata.keys, 'recorder'
38
+ assert_equal({ name: 'cucumber' }.stringify_keys, metadata['recorder'])
39
+
40
+ assert_includes metadata.keys, 'frameworks'
41
+ cucumber = metadata['frameworks'].select {|f| f['name'] == 'cucumber'}
42
+ assert_equal 1, cucumber.count
43
+ end
44
+ end
45
+
46
+ def test_cucumber4
47
+ perform_test 'cucumber4_recorder' do
48
+ appmap_file = 'tmp/appmap/cucumber/Say_hello.appmap.json'
49
+
50
+ assert File.file?(appmap_file),
51
+ %(appmap output file does not exist in #{Dir.new('tmp/appmap/cucumber').entries.join(', ')})
52
+ appmap = JSON.parse(File.read(appmap_file))
53
+ assert_equal AppMap::APPMAP_FORMAT_VERSION, appmap['version']
54
+ assert_includes appmap.keys, 'metadata'
55
+ metadata = appmap['metadata']
56
+
57
+ assert_equal 'say_hello', metadata['feature_group']
58
+ # In cucumber4, there's no access to the feature name from within the executing scenario
59
+ # (as far as I can tell).
60
+ assert_equal 'Say hello', metadata['feature']
61
+ assert_equal 'Say hello', metadata['name']
62
+ assert_includes metadata.keys, 'client'
63
+ assert_equal({ name: 'appmap', url: AppMap::URL, version: AppMap::VERSION }.stringify_keys, metadata['client'])
64
+ assert_includes metadata.keys, 'recorder'
65
+ assert_equal({ name: 'cucumber' }.stringify_keys, metadata['recorder'])
66
+
67
+ assert_includes metadata.keys, 'frameworks'
68
+ cucumber = metadata['frameworks'].select {|f| f['name'] == 'cucumber'}
69
+ assert_equal 1, cucumber.count
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'appmap', git: 'applandinc/appmap-ruby', branch: `git rev-parse --abbrev-ref HEAD`.strip
4
+ gem 'byebug'
5
+ gem 'cucumber', '>= 4'
@@ -0,0 +1,3 @@
1
+ name: cucumber_recorder
2
+ packages:
3
+ - path: lib
@@ -0,0 +1,5 @@
1
+ Feature: I can say hello
2
+
3
+ Scenario: Say hello
4
+ When I say hello
5
+ Then the message is hello
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber'
4
+ require 'appmap/cucumber'
5
+ require File.join(__dir__, '../../lib/hello')
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'appmap'
4
+
5
+ Around('not @appmap-disable') do |scenario, block|
6
+ appmap = AppMap.record do
7
+ block.call
8
+ end
9
+
10
+ AppMap::Cucumber.write_scenario(scenario, appmap)
11
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ When('I say hello') do
4
+ @message = Hello.new.say_hello
5
+ end
6
+
7
+ Then('the message is hello') do
8
+ raise 'Wrong message!' unless @message == 'Hello!'
9
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hello
4
+ def say_hello
5
+ 'Hello!'
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'appmap', git: 'applandinc/appmap-ruby', branch: `git rev-parse --abbrev-ref HEAD`.strip
4
+ gem 'byebug'
5
+ gem 'cucumber', '< 4'
@@ -0,0 +1,3 @@
1
+ name: cucumber_recorder
2
+ packages:
3
+ - path: lib
@@ -0,0 +1,5 @@
1
+ Feature: I can say hello
2
+
3
+ Scenario: Say hello
4
+ When I say hello
5
+ Then the message is hello
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber'
4
+ require 'appmap/cucumber'
5
+ require File.join(__dir__, '../../lib/hello')
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'appmap'
4
+
5
+ Around('not @appmap-disable') do |scenario, block|
6
+ appmap = AppMap.record do
7
+ block.call
8
+ end
9
+
10
+ AppMap::Cucumber.write_scenario(scenario, appmap)
11
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ When('I say hello') do
4
+ @message = Hello.new.say_hello
5
+ end
6
+
7
+ Then('the message is hello') do
8
+ raise 'Wrong message!' unless @message == 'Hello!'
9
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hello
4
+ def say_hello
5
+ 'Hello!'
6
+ end
7
+ end
@@ -1,5 +1,5 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'appmap', git: '../../..', branch: `git rev-parse --abbrev-ref HEAD`.strip
3
+ gem 'appmap', git: 'applandinc/appmap-ruby', branch: `git rev-parse --abbrev-ref HEAD`.strip
4
4
  gem 'byebug'
5
5
  gem 'rspec'
@@ -3,6 +3,18 @@ require 'appmap/rspec'
3
3
  require 'hello'
4
4
 
5
5
  describe Hello, feature_group: 'Saying hello' do
6
+ before do
7
+ # Trick appmap-ruby into thinking we're a Rails app.
8
+ stub_const('Rails', double('rails', version: 'fake.0'))
9
+ end
10
+
11
+ # The order of these examples is important. The tests check the
12
+ # appmap for 'says hello', and we want another example to get run
13
+ # before it.
14
+ it 'does not say goodbye', feature: 'Speak hello', appmap: true do
15
+ expect(Hello.new.say_hello).not_to eq('Goodbye!')
16
+ end
17
+
6
18
  it 'says hello', feature: 'Speak hello', appmap: true do
7
19
  expect(Hello.new.say_hello).to eq('Hello!')
8
20
  end
@@ -9,6 +9,7 @@ class RSpecTest < Minitest::Test
9
9
  Bundler.with_clean_env do
10
10
  Dir.chdir 'test/fixtures/rspec_recorder' do
11
11
  FileUtils.rm_rf 'tmp'
12
+ system 'bundle config --local local.appmap ../../..'
12
13
  system 'bundle'
13
14
  system({ 'APPMAP' => 'true' }, %(bundle exec rspec spec/#{test_name}.rb))
14
15
 
@@ -48,6 +49,10 @@ class RSpecTest < Minitest::Test
48
49
  assert_equal({ name: 'appmap', url: AppMap::URL, version: AppMap::VERSION }.stringify_keys, metadata['client'])
49
50
  assert_includes metadata.keys, 'recorder'
50
51
  assert_equal({ name: 'rspec' }.stringify_keys, metadata['recorder'])
52
+
53
+ assert_includes metadata.keys, 'frameworks'
54
+ rspec = metadata['frameworks'].select {|f| f['name'] == 'rspec'}
55
+ assert_equal 1, rspec.count
51
56
  end
52
57
  end
53
58
 
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.25.2
4
+ version: 0.28.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Gilpin
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-05-04 00:00:00.000000000 Z
11
+ date: 2020-07-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -269,15 +269,17 @@ files:
269
269
  - lib/appmap/class_map.rb
270
270
  - lib/appmap/command/record.rb
271
271
  - lib/appmap/command/stats.rb
272
- - lib/appmap/command/upload.rb
272
+ - lib/appmap/cucumber.rb
273
273
  - lib/appmap/event.rb
274
274
  - lib/appmap/hook.rb
275
+ - lib/appmap/metadata.rb
275
276
  - lib/appmap/middleware/remote_recording.rb
276
277
  - lib/appmap/rails/action_handler.rb
277
278
  - lib/appmap/rails/sql_handler.rb
278
279
  - lib/appmap/railtie.rb
279
280
  - lib/appmap/rspec.rb
280
281
  - lib/appmap/trace.rb
282
+ - lib/appmap/util.rb
281
283
  - lib/appmap/version.rb
282
284
  - lore/pages/2019-05-21-install-and-record/index.pug
283
285
  - lore/pages/2019-05-21-install-and-record/install_example_appmap.png
@@ -302,10 +304,10 @@ files:
302
304
  - spec/abstract_controller_base_spec.rb
303
305
  - spec/config_spec.rb
304
306
  - spec/fixtures/hook/attr_accessor.rb
305
- - spec/fixtures/hook/class_method.rb
306
307
  - spec/fixtures/hook/constructor.rb
307
308
  - spec/fixtures/hook/exception_method.rb
308
309
  - spec/fixtures/hook/instance_method.rb
310
+ - spec/fixtures/hook/singleton_method.rb
309
311
  - spec/fixtures/rack_users_app/.dockerignore
310
312
  - spec/fixtures/rack_users_app/.gitignore
311
313
  - spec/fixtures/rack_users_app/Dockerfile
@@ -437,6 +439,10 @@ files:
437
439
  - spec/fixtures/rails_users_app/db/migrate/20190728211408_create_users.rb
438
440
  - spec/fixtures/rails_users_app/db/schema.rb
439
441
  - spec/fixtures/rails_users_app/docker-compose.yml
442
+ - spec/fixtures/rails_users_app/features/api_users.feature
443
+ - spec/fixtures/rails_users_app/features/support/env.rb
444
+ - spec/fixtures/rails_users_app/features/support/hooks.rb
445
+ - spec/fixtures/rails_users_app/features/support/steps.rb
440
446
  - spec/fixtures/rails_users_app/lib/tasks/.keep
441
447
  - spec/fixtures/rails_users_app/log/.keep
442
448
  - spec/fixtures/rails_users_app/public/robots.txt
@@ -453,9 +459,25 @@ files:
453
459
  - spec/remote_recording_spec.rb
454
460
  - spec/rspec_feature_metadata_spec.rb
455
461
  - spec/spec_helper.rb
462
+ - spec/util_spec.rb
456
463
  - test/cli_test.rb
464
+ - test/cucumber_test.rb
457
465
  - test/fixtures/cli_record_test/appmap.yml
458
466
  - test/fixtures/cli_record_test/lib/cli_record_test/main.rb
467
+ - test/fixtures/cucumber4_recorder/Gemfile
468
+ - test/fixtures/cucumber4_recorder/appmap.yml
469
+ - test/fixtures/cucumber4_recorder/features/say_hello.feature
470
+ - test/fixtures/cucumber4_recorder/features/support/env.rb
471
+ - test/fixtures/cucumber4_recorder/features/support/hooks.rb
472
+ - test/fixtures/cucumber4_recorder/features/support/steps.rb
473
+ - test/fixtures/cucumber4_recorder/lib/hello.rb
474
+ - test/fixtures/cucumber_recorder/Gemfile
475
+ - test/fixtures/cucumber_recorder/appmap.yml
476
+ - test/fixtures/cucumber_recorder/features/say_hello.feature
477
+ - test/fixtures/cucumber_recorder/features/support/env.rb
478
+ - test/fixtures/cucumber_recorder/features/support/hooks.rb
479
+ - test/fixtures/cucumber_recorder/features/support/steps.rb
480
+ - test/fixtures/cucumber_recorder/lib/hello.rb
459
481
  - test/fixtures/rspec_recorder/Gemfile
460
482
  - test/fixtures/rspec_recorder/appmap.yml
461
483
  - test/fixtures/rspec_recorder/lib/hello.rb
@@ -1,101 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
- require 'faraday'
5
-
6
- module AppMap
7
- module Command
8
- UploadResponse = Struct.new(:batch_id, :scenario_uuid)
9
-
10
- UploadStruct = Struct.new(:data, :url, :user, :org)
11
- class Upload < UploadStruct
12
- MAX_DEPTH = 12
13
-
14
- attr_accessor :batch_id
15
-
16
- def initialize(data, url, user, org)
17
- super
18
-
19
- # TODO: Make this an option
20
- @max_depth = MAX_DEPTH
21
- end
22
-
23
- def perform
24
- appmap = data.clone
25
-
26
- events = data.delete('events')
27
- class_map = data.delete('classMap') || []
28
-
29
- unless events.blank?
30
- pruned_events = []
31
- stack = []
32
- events.each do |evt|
33
- if evt['event'] == 'call'
34
- stack << evt
35
- stack_depth = stack.length
36
- else
37
- stack_depth = stack.length
38
- stack.pop
39
- end
40
-
41
- prune = stack_depth > @max_depth
42
-
43
- pruned_events << evt unless prune
44
- end
45
-
46
- warn "Pruned events to #{pruned_events.length}" if events.length > pruned_events.length
47
-
48
- appmap[:events] = pruned_events
49
- appmap[:classMap] = prune(class_map, events: pruned_events)
50
- else
51
- appmap[:events] = []
52
- appmap[:classMap] = prune(class_map)
53
- end
54
-
55
- upload_file = { user: user, org: org, data: appmap }.compact
56
-
57
- conn = Faraday.new(url: url)
58
- response = conn.post do |req|
59
- req.url '/api/scenarios'
60
- req.headers['Content-Type'] = 'application/json'
61
- req.headers[AppMap::BATCH_HEADER_NAME] = @batch_id if @batch_id
62
- req.body = JSON.generate(upload_file)
63
- end
64
-
65
- unless response.body.blank?
66
- message = begin
67
- JSON.parse(response.body)
68
- rescue JSON::ParserError => e
69
- warn "Response is not valid JSON (#{e.message})"
70
- nil
71
- end
72
- end
73
-
74
- unless response.success?
75
- error = [ 'Upload failed' ]
76
- error << ": #{message}" if message
77
- raise error.join
78
- end
79
-
80
- batch_id = @batch_id || response.headers[AppMap::BATCH_HEADER_NAME]
81
-
82
- uuid = message['uuid']
83
- UploadResponse.new(batch_id, uuid)
84
- end
85
-
86
- protected
87
-
88
- def debug?
89
- ENV['DEBUG'] == 'true' || ENV['GLI_DEBUG'] == 'true'
90
- end
91
-
92
- def prune(class_map, events: nil)
93
- require 'appmap/algorithm/prune_class_map'
94
- Algorithm::PruneClassMap.new(class_map).tap do |alg|
95
- alg.events = events if events
96
- alg.logger = ->(msg) { warn msg } if debug?
97
- end.perform
98
- end
99
- end
100
- end
101
- end