appmap 0.25.2 → 0.28.1

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