appmap 0.25.0 → 0.28.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -2
- data/CHANGELOG.md +33 -0
- data/README.md +123 -39
- data/exe/appmap +3 -57
- data/lib/appmap.rb +51 -32
- data/lib/appmap/command/record.rb +2 -61
- data/lib/appmap/cucumber.rb +89 -0
- data/lib/appmap/event.rb +1 -1
- data/lib/appmap/hook.rb +7 -1
- data/lib/appmap/metadata.rb +62 -0
- data/lib/appmap/middleware/remote_recording.rb +2 -7
- data/lib/appmap/rails/sql_handler.rb +0 -5
- data/lib/appmap/railtie.rb +2 -2
- data/lib/appmap/rspec.rb +20 -38
- data/lib/appmap/trace.rb +9 -9
- data/lib/appmap/util.rb +40 -0
- data/lib/appmap/version.rb +1 -1
- data/spec/fixtures/rails_users_app/Gemfile +1 -0
- data/spec/fixtures/rails_users_app/features/api_users.feature +13 -0
- data/spec/fixtures/rails_users_app/features/support/env.rb +4 -0
- data/spec/fixtures/rails_users_app/features/support/hooks.rb +11 -0
- data/spec/fixtures/rails_users_app/features/support/steps.rb +18 -0
- data/spec/hook_spec.rb +21 -3
- data/spec/rails_spec_helper.rb +2 -0
- data/spec/rspec_feature_metadata_spec.rb +2 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/util_spec.rb +21 -0
- data/test/cli_test.rb +0 -13
- data/test/cucumber_test.rb +72 -0
- data/test/fixtures/cucumber4_recorder/Gemfile +5 -0
- data/test/fixtures/cucumber4_recorder/appmap.yml +3 -0
- data/test/fixtures/cucumber4_recorder/features/say_hello.feature +5 -0
- data/test/fixtures/cucumber4_recorder/features/support/env.rb +5 -0
- data/test/fixtures/cucumber4_recorder/features/support/hooks.rb +11 -0
- data/test/fixtures/cucumber4_recorder/features/support/steps.rb +9 -0
- data/test/fixtures/cucumber4_recorder/lib/hello.rb +7 -0
- data/test/fixtures/cucumber_recorder/Gemfile +5 -0
- data/test/fixtures/cucumber_recorder/appmap.yml +3 -0
- data/test/fixtures/cucumber_recorder/features/say_hello.feature +5 -0
- data/test/fixtures/cucumber_recorder/features/support/env.rb +5 -0
- data/test/fixtures/cucumber_recorder/features/support/hooks.rb +11 -0
- data/test/fixtures/cucumber_recorder/features/support/steps.rb +9 -0
- data/test/fixtures/cucumber_recorder/lib/hello.rb +7 -0
- data/test/fixtures/rspec_recorder/Gemfile +1 -1
- data/test/fixtures/rspec_recorder/spec/decorated_hello_spec.rb +12 -0
- data/test/rspec_test.rb +5 -0
- metadata +26 -3
- data/lib/appmap/command/upload.rb +0 -101
data/lib/appmap/util.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppMap
|
4
|
+
module Util
|
5
|
+
class << self
|
6
|
+
# scenario_filename builds a suitable file name from a scenario name.
|
7
|
+
# Special characters are removed, and the file name is truncated to fit within
|
8
|
+
# shell limitations.
|
9
|
+
def scenario_filename(name, max_length: 255, separator: '_', extension: '.appmap.json')
|
10
|
+
# Cribbed from v5 version of ActiveSupport:Inflector#parameterize:
|
11
|
+
# https://github.com/rails/rails/blob/v5.2.4/activesupport/lib/active_support/inflector/transliterate.rb#L92
|
12
|
+
# Replace accented chars with their ASCII equivalents.
|
13
|
+
|
14
|
+
fname = name.encode('utf-8', invalid: :replace, undef: :replace, replace: '_')
|
15
|
+
|
16
|
+
# Turn unwanted chars into the separator.
|
17
|
+
fname.gsub!(/[^a-z0-9\-_]+/i, separator)
|
18
|
+
|
19
|
+
re_sep = Regexp.escape(separator)
|
20
|
+
re_duplicate_separator = /#{re_sep}{2,}/
|
21
|
+
re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i
|
22
|
+
|
23
|
+
# No more than one of the separator in a row.
|
24
|
+
fname.gsub!(re_duplicate_separator, separator)
|
25
|
+
|
26
|
+
# Finally, Remove leading/trailing separator.
|
27
|
+
fname.gsub!(re_leading_trailing_separator, '')
|
28
|
+
|
29
|
+
if (fname.length + extension.length) > max_length
|
30
|
+
require 'base64'
|
31
|
+
require 'digest'
|
32
|
+
fname_digest = Base64.urlsafe_encode64 Digest::MD5.digest(fname), padding: false
|
33
|
+
fname[max_length - fname_digest.length - extension.length - 1..-1] = [ '-', fname_digest ].join
|
34
|
+
end
|
35
|
+
|
36
|
+
[ fname, extension ].join
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/appmap/version.rb
CHANGED
@@ -39,6 +39,7 @@ appmap_options = \
|
|
39
39
|
gem 'appmap', appmap_options
|
40
40
|
|
41
41
|
group :development, :test do
|
42
|
+
gem 'cucumber-rails', require: false
|
42
43
|
gem 'rspec-rails'
|
43
44
|
# Required for Sequel, since without ActiveRecord, the Rails transactional fixture support
|
44
45
|
# isn't activated.
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Feature: /api/users
|
2
|
+
|
3
|
+
@appmap-disable
|
4
|
+
Scenario: A user can be created
|
5
|
+
When I create a user
|
6
|
+
Then the response status should be 201
|
7
|
+
|
8
|
+
Scenario: When a user is created, it should be in the user list
|
9
|
+
Given I create a user
|
10
|
+
And the response status should be 201
|
11
|
+
When I list the users
|
12
|
+
Then the response status should be 200
|
13
|
+
And the response should include the user
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
When 'I create a user' do
|
4
|
+
@response = post '/api/users', login: 'alice'
|
5
|
+
end
|
6
|
+
|
7
|
+
Then(/the response status should be (\d+)/) do |status|
|
8
|
+
expect(@response.status).to eq(status.to_i)
|
9
|
+
end
|
10
|
+
|
11
|
+
When 'I list the users' do
|
12
|
+
@response = get '/api/users'
|
13
|
+
@users = JSON.parse(@response.body)
|
14
|
+
end
|
15
|
+
|
16
|
+
Then 'the response should include the user' do
|
17
|
+
expect(@users.map { |u| u['login'] }).to include('alice')
|
18
|
+
end
|
data/spec/hook_spec.rb
CHANGED
@@ -31,8 +31,10 @@ describe 'AppMap class Hooking' do
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def invoke_test_file(file, &block)
|
34
|
+
AppMap.configuration = nil
|
34
35
|
package = AppMap::Hook::Package.new(file, [])
|
35
36
|
config = AppMap::Hook::Config.new('hook_spec', [ package ])
|
37
|
+
AppMap.configuration = config
|
36
38
|
AppMap::Hook.hook(config)
|
37
39
|
|
38
40
|
tracer = AppMap.tracing.trace
|
@@ -55,6 +57,10 @@ describe 'AppMap class Hooking' do
|
|
55
57
|
[ config, tracer ]
|
56
58
|
end
|
57
59
|
|
60
|
+
after do
|
61
|
+
AppMap.configuration = nil
|
62
|
+
end
|
63
|
+
|
58
64
|
it 'hooks an instance method that takes no arguments' do
|
59
65
|
events_yaml = <<~YAML
|
60
66
|
---
|
@@ -90,10 +96,10 @@ describe 'AppMap class Hooking' do
|
|
90
96
|
end
|
91
97
|
|
92
98
|
it 'builds a class map of invoked methods' do
|
93
|
-
|
99
|
+
_, tracer = invoke_test_file 'spec/fixtures/hook/instance_method.rb' do
|
94
100
|
InstanceMethod.new.say_default
|
95
101
|
end
|
96
|
-
class_map = AppMap.class_map(
|
102
|
+
class_map = AppMap.class_map(tracer.event_methods).to_yaml
|
97
103
|
expect(Diffy::Diff.new(class_map, <<~YAML).to_s).to eq('')
|
98
104
|
---
|
99
105
|
- :name: spec/fixtures/hook/instance_method.rb
|
@@ -351,7 +357,19 @@ describe 'AppMap class Hooking' do
|
|
351
357
|
:lineno: 9
|
352
358
|
YAML
|
353
359
|
test_hook_behavior 'spec/fixtures/hook/exception_method.rb', events_yaml do
|
354
|
-
|
360
|
+
begin
|
361
|
+
ExceptionMethod.new.raise_exception
|
362
|
+
rescue
|
363
|
+
# don't let the exception fail the test
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
it 're-raises exceptions' do
|
369
|
+
RSpec::Expectations.configuration.on_potential_false_positives = :nothing
|
370
|
+
|
371
|
+
invoke_test_file 'spec/fixtures/hook/exception_method.rb' do
|
372
|
+
expect { ExceptionMethod.new.raise_exception }.to raise_exception
|
355
373
|
end
|
356
374
|
end
|
357
375
|
end
|
data/spec/rails_spec_helper.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -4,4 +4,12 @@ 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'
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
config.example_status_persistence_file_path = "tmp/rspec_failed_examples.txt"
|
15
|
+
end
|
data/spec/util_spec.rb
ADDED
@@ -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
|
data/test/cli_test.rb
CHANGED
@@ -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 --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
|
@@ -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
|