appmap 0.41.1 → 0.44.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/.travis.yml +17 -2
- data/CHANGELOG.md +31 -0
- data/README.md +54 -22
- data/appmap.gemspec +0 -2
- data/lib/appmap.rb +3 -2
- data/lib/appmap/class_map.rb +7 -10
- data/lib/appmap/config.rb +94 -34
- data/lib/appmap/cucumber.rb +1 -1
- data/lib/appmap/event.rb +18 -0
- data/lib/appmap/hook.rb +42 -22
- data/lib/appmap/hook/method.rb +1 -1
- data/lib/appmap/minitest.rb +35 -30
- data/lib/appmap/rails/request_handler.rb +41 -10
- data/lib/appmap/record.rb +1 -1
- data/lib/appmap/rspec.rb +32 -96
- data/lib/appmap/util.rb +16 -0
- data/lib/appmap/version.rb +1 -1
- data/patch +1447 -0
- data/spec/abstract_controller_base_spec.rb +69 -26
- data/spec/class_map_spec.rb +3 -11
- data/spec/config_spec.rb +31 -1
- data/spec/fixtures/hook/custom_instance_method.rb +11 -0
- data/spec/fixtures/hook/method_named_call.rb +11 -0
- data/spec/fixtures/rails5_users_app/Gemfile +7 -3
- data/spec/fixtures/rails5_users_app/app/controllers/api/users_controller.rb +2 -0
- data/spec/fixtures/rails5_users_app/app/controllers/users_controller.rb +9 -1
- data/spec/fixtures/rails5_users_app/config/application.rb +2 -0
- data/spec/fixtures/rails5_users_app/create_app +8 -2
- data/spec/fixtures/rails5_users_app/docker-compose.yml +3 -0
- data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_api_spec.rb +16 -3
- data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_spec.rb +2 -2
- data/spec/fixtures/rails5_users_app/spec/models/user_spec.rb +2 -12
- data/spec/fixtures/rails5_users_app/spec/rails_helper.rb +3 -9
- data/spec/fixtures/rails6_users_app/Gemfile +5 -4
- data/spec/fixtures/rails6_users_app/app/controllers/api/users_controller.rb +1 -0
- data/spec/fixtures/rails6_users_app/app/controllers/users_controller.rb +9 -1
- data/spec/fixtures/rails6_users_app/config/application.rb +2 -0
- data/spec/fixtures/rails6_users_app/create_app +8 -2
- data/spec/fixtures/rails6_users_app/docker-compose.yml +3 -0
- data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_api_spec.rb +16 -3
- data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_spec.rb +2 -2
- data/spec/fixtures/rails6_users_app/spec/models/user_spec.rb +2 -12
- data/spec/fixtures/rails6_users_app/spec/rails_helper.rb +3 -9
- data/spec/hook_spec.rb +134 -10
- data/spec/record_sql_rails_pg_spec.rb +1 -1
- data/spec/spec_helper.rb +6 -0
- data/test/expectations/openssl_test_key_sign1.json +2 -4
- data/test/fixtures/rspec_recorder/spec/decorated_hello_spec.rb +3 -3
- data/test/fixtures/rspec_recorder/spec/plain_hello_spec.rb +1 -1
- data/test/gem_test.rb +1 -1
- data/test/minitest_test.rb +1 -2
- data/test/rspec_test.rb +1 -20
- metadata +7 -8
- data/exe/appmap +0 -154
- data/spec/rspec_feature_metadata_spec.rb +0 -31
- data/test/cli_test.rb +0 -116
data/lib/appmap/cucumber.rb
CHANGED
@@ -50,7 +50,7 @@ module AppMap
|
|
50
50
|
appmap['metadata'] = update_metadata(scenario, appmap['metadata'])
|
51
51
|
scenario_filename = AppMap::Util.scenario_filename(appmap['metadata']['name'])
|
52
52
|
|
53
|
-
|
53
|
+
AppMap::Util.write_appmap(File.join(APPMAP_OUTPUT_DIR, scenario_filename), JSON.generate(appmap))
|
54
54
|
end
|
55
55
|
|
56
56
|
def enabled?
|
data/lib/appmap/event.rb
CHANGED
@@ -36,6 +36,18 @@ module AppMap
|
|
36
36
|
(value_string||'')[0...LIMIT].encode('utf-8', invalid: :replace, undef: :replace, replace: '_')
|
37
37
|
end
|
38
38
|
|
39
|
+
def object_properties(hash_like)
|
40
|
+
hash = hash_like.to_h
|
41
|
+
hash.keys.map do |key|
|
42
|
+
{
|
43
|
+
name: key,
|
44
|
+
class: hash[key].class.name,
|
45
|
+
}
|
46
|
+
end
|
47
|
+
rescue
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
|
39
51
|
protected
|
40
52
|
|
41
53
|
# Heuristic for dynamically defined class whose name can be nil
|
@@ -79,6 +91,12 @@ module AppMap
|
|
79
91
|
end
|
80
92
|
end
|
81
93
|
end
|
94
|
+
|
95
|
+
protected
|
96
|
+
|
97
|
+
def object_properties(hash_like)
|
98
|
+
self.class.object_properties(hash_like)
|
99
|
+
end
|
82
100
|
end
|
83
101
|
|
84
102
|
class MethodCall < MethodEvent
|
data/lib/appmap/hook.rb
CHANGED
@@ -32,6 +32,7 @@ module AppMap
|
|
32
32
|
end
|
33
33
|
|
34
34
|
attr_reader :config
|
35
|
+
|
35
36
|
def initialize(config)
|
36
37
|
@config = config
|
37
38
|
end
|
@@ -46,10 +47,23 @@ module AppMap
|
|
46
47
|
cls = trace_point.self
|
47
48
|
|
48
49
|
instance_methods = cls.public_instance_methods(false) - OBJECT_INSTANCE_METHODS
|
49
|
-
|
50
|
+
# NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
|
51
|
+
class_methods = begin
|
52
|
+
if cls.respond_to?(:singleton_class)
|
53
|
+
cls.singleton_class.public_instance_methods(false) - instance_methods - OBJECT_STATIC_METHODS
|
54
|
+
else
|
55
|
+
[]
|
56
|
+
end
|
57
|
+
rescue NameError
|
58
|
+
[]
|
59
|
+
end
|
50
60
|
|
51
61
|
hook = lambda do |hook_cls|
|
52
62
|
lambda do |method_id|
|
63
|
+
# Don't try and trace the AppMap methods or there will be
|
64
|
+
# a stack overflow in the defined hook method.
|
65
|
+
return if (hook_cls&.name || '').split('::')[0] == AppMap.name
|
66
|
+
|
53
67
|
method = begin
|
54
68
|
hook_cls.public_instance_method(method_id)
|
55
69
|
rescue NameError
|
@@ -69,18 +83,22 @@ module AppMap
|
|
69
83
|
config.always_hook?(hook_cls, method.name) ||
|
70
84
|
config.included_by_location?(method)
|
71
85
|
|
72
|
-
|
86
|
+
package = config.package_for_method(method)
|
73
87
|
|
74
|
-
|
75
|
-
# a stack overflow in the defined hook method.
|
76
|
-
next if /\AAppMap[:\.]/.match?(hook_method.method_display_name)
|
88
|
+
hook_method = Hook::Method.new(package, hook_cls, method)
|
77
89
|
|
78
90
|
hook_method.activate
|
79
91
|
end
|
80
92
|
end
|
81
93
|
|
82
94
|
instance_methods.each(&hook.(cls))
|
83
|
-
|
95
|
+
# NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
|
96
|
+
begin
|
97
|
+
class_methods.each(&hook.(cls.singleton_class)) if cls.respond_to?(:singleton_class)
|
98
|
+
rescue NameError
|
99
|
+
# NameError:
|
100
|
+
# uninitialized constant Faraday::Connection
|
101
|
+
end
|
84
102
|
end
|
85
103
|
|
86
104
|
tp.enable(&block)
|
@@ -97,25 +115,27 @@ module AppMap
|
|
97
115
|
end
|
98
116
|
end
|
99
117
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
118
|
+
config.builtin_methods.each do |class_name, hooks|
|
119
|
+
Array(hooks).each do |hook|
|
120
|
+
require hook.package.package_name if hook.package.package_name
|
121
|
+
Array(hook.method_names).each do |method_name|
|
122
|
+
method_name = method_name.to_sym
|
104
123
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
124
|
+
cls = class_from_string.(class_name)
|
125
|
+
method = \
|
126
|
+
begin
|
127
|
+
cls.instance_method(method_name)
|
128
|
+
rescue NameError
|
129
|
+
cls.method(method_name) rescue nil
|
130
|
+
end
|
112
131
|
|
113
|
-
|
132
|
+
next if config.never_hook?(method)
|
114
133
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
134
|
+
if method
|
135
|
+
Hook::Method.new(hook.package, cls, method).activate
|
136
|
+
else
|
137
|
+
warn "Method #{method_name} not found on #{cls.name}"
|
138
|
+
end
|
119
139
|
end
|
120
140
|
end
|
121
141
|
end
|
data/lib/appmap/hook/method.rb
CHANGED
data/lib/appmap/minitest.rb
CHANGED
@@ -7,22 +7,28 @@ module AppMap
|
|
7
7
|
# be activated around each test.
|
8
8
|
module Minitest
|
9
9
|
APPMAP_OUTPUT_DIR = 'tmp/appmap/minitest'
|
10
|
-
LOG =
|
10
|
+
LOG = ( ENV['APPMAP_DEBUG'] == 'true' || ENV['DEBUG'] == 'true' )
|
11
11
|
|
12
12
|
def self.metadata
|
13
13
|
AppMap.detect_metadata
|
14
14
|
end
|
15
15
|
|
16
|
-
Recording = Struct.new(:test) do
|
17
|
-
def initialize(test)
|
16
|
+
Recording = Struct.new(:test, :test_name) do
|
17
|
+
def initialize(test, test_name)
|
18
18
|
super
|
19
19
|
|
20
|
-
warn "Starting recording of test #{test.class}.#{test.name}" if AppMap::Minitest::LOG
|
20
|
+
warn "Starting recording of test #{test.class}.#{test.name}@#{source_location}" if AppMap::Minitest::LOG
|
21
21
|
@trace = AppMap.tracing.trace
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
24
|
+
def source_location
|
25
|
+
test.method(test_name).source_location.join(':')
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def finish(exception)
|
25
30
|
warn "Finishing recording of test #{test.class}.#{test.name}" if AppMap::Minitest::LOG
|
31
|
+
warn "Exception: #{exception}" if exception && AppMap::Minitest::LOG
|
26
32
|
|
27
33
|
events = []
|
28
34
|
AppMap.tracing.delete @trace
|
@@ -37,11 +43,12 @@ module AppMap
|
|
37
43
|
feature_name = test.name.split('_')[1..-1].join(' ')
|
38
44
|
scenario_name = [ feature_group, feature_name ].join(' ')
|
39
45
|
|
40
|
-
AppMap::Minitest.save scenario_name,
|
41
|
-
class_map,
|
42
|
-
|
43
|
-
|
44
|
-
|
46
|
+
AppMap::Minitest.save name: scenario_name,
|
47
|
+
class_map: class_map,
|
48
|
+
source_location: source_location,
|
49
|
+
test_status: exception ? 'failed' : 'succeeded',
|
50
|
+
exception: exception,
|
51
|
+
events: events
|
45
52
|
end
|
46
53
|
end
|
47
54
|
|
@@ -55,15 +62,15 @@ module AppMap
|
|
55
62
|
FileUtils.mkdir_p APPMAP_OUTPUT_DIR
|
56
63
|
end
|
57
64
|
|
58
|
-
def begin_test(test)
|
59
|
-
@recordings_by_test[test.object_id] = Recording.new(test)
|
65
|
+
def begin_test(test, name)
|
66
|
+
@recordings_by_test[test.object_id] = Recording.new(test, name)
|
60
67
|
end
|
61
68
|
|
62
|
-
def end_test(test)
|
69
|
+
def end_test(test, exception:)
|
63
70
|
recording = @recordings_by_test.delete(test.object_id)
|
64
71
|
return warn "No recording found for #{test}" unless recording
|
65
72
|
|
66
|
-
recording.finish
|
73
|
+
recording.finish exception
|
67
74
|
end
|
68
75
|
|
69
76
|
def config
|
@@ -74,12 +81,11 @@ module AppMap
|
|
74
81
|
@event_methods += event_methods
|
75
82
|
end
|
76
83
|
|
77
|
-
def save(
|
84
|
+
def save(name:, class_map:, source_location:, test_status:, exception:, events:)
|
78
85
|
metadata = AppMap::Minitest.metadata.tap do |m|
|
79
|
-
m[:name] =
|
86
|
+
m[:name] = name
|
87
|
+
m[:source_location] = source_location
|
80
88
|
m[:app] = AppMap.configuration.name
|
81
|
-
m[:feature] = feature_name if feature_name
|
82
|
-
m[:feature_group] = feature_group_name if feature_group_name
|
83
89
|
m[:frameworks] ||= []
|
84
90
|
m[:frameworks] << {
|
85
91
|
name: 'minitest',
|
@@ -88,6 +94,13 @@ module AppMap
|
|
88
94
|
m[:recorder] = {
|
89
95
|
name: 'minitest'
|
90
96
|
}
|
97
|
+
m[:test_status] = test_status
|
98
|
+
if exception
|
99
|
+
m[:exception] = {
|
100
|
+
class: exception.class.name,
|
101
|
+
message: exception.to_s
|
102
|
+
}
|
103
|
+
end
|
91
104
|
end
|
92
105
|
|
93
106
|
appmap = {
|
@@ -96,14 +109,9 @@ module AppMap
|
|
96
109
|
classMap: class_map,
|
97
110
|
events: events
|
98
111
|
}.compact
|
99
|
-
fname = AppMap::Util.scenario_filename(
|
112
|
+
fname = AppMap::Util.scenario_filename(name)
|
100
113
|
|
101
|
-
|
102
|
-
end
|
103
|
-
|
104
|
-
def print_inventory
|
105
|
-
class_map = AppMap.class_map(@event_methods)
|
106
|
-
save 'Inventory', class_map, labels: %w[inventory]
|
114
|
+
AppMap::Util.write_appmap(File.join(APPMAP_OUTPUT_DIR, fname), JSON.generate(appmap))
|
107
115
|
end
|
108
116
|
|
109
117
|
def enabled?
|
@@ -112,9 +120,6 @@ module AppMap
|
|
112
120
|
|
113
121
|
def run
|
114
122
|
init
|
115
|
-
at_exit do
|
116
|
-
print_inventory
|
117
|
-
end
|
118
123
|
end
|
119
124
|
end
|
120
125
|
end
|
@@ -128,11 +133,11 @@ if AppMap::Minitest.enabled?
|
|
128
133
|
alias run_without_hook run
|
129
134
|
|
130
135
|
def run
|
131
|
-
AppMap::Minitest.begin_test self
|
136
|
+
AppMap::Minitest.begin_test self, name
|
132
137
|
begin
|
133
138
|
run_without_hook
|
134
139
|
ensure
|
135
|
-
AppMap::Minitest.end_test self
|
140
|
+
AppMap::Minitest.end_test self, exception: $!
|
136
141
|
end
|
137
142
|
end
|
138
143
|
end
|
@@ -6,15 +6,38 @@ require 'appmap/hook'
|
|
6
6
|
module AppMap
|
7
7
|
module Rails
|
8
8
|
module RequestHandler
|
9
|
+
# Host and User-Agent will just introduce needless variation.
|
10
|
+
# Content-Type and Authorization get their own fields in the request.
|
11
|
+
IGNORE_HEADERS = %w[host user_agent content_type authorization].map(&:upcase).map {|h| "HTTP_#{h}"}.freeze
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def selected_headers(env)
|
15
|
+
# Rack prepends HTTP_ to all client-sent headers.
|
16
|
+
matching_headers = env
|
17
|
+
.select { |k,v| k.start_with? 'HTTP_'}
|
18
|
+
.reject { |k,v| IGNORE_HEADERS.member?(k) }
|
19
|
+
.reject { |k,v| v.blank? }
|
20
|
+
.each_with_object({}) do |kv, memo|
|
21
|
+
key = kv[0].sub(/^HTTP_/, '').split('_').map(&:capitalize).join('-')
|
22
|
+
value = kv[1]
|
23
|
+
memo[key] = value
|
24
|
+
end
|
25
|
+
matching_headers.blank? ? nil : matching_headers
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
9
29
|
class HTTPServerRequest < AppMap::Event::MethodEvent
|
10
|
-
attr_accessor :normalized_path_info, :request_method, :path_info, :params
|
30
|
+
attr_accessor :normalized_path_info, :request_method, :path_info, :params, :mime_type, :headers, :authorization
|
11
31
|
|
12
32
|
def initialize(request)
|
13
33
|
super AppMap::Event.next_id_counter, :call, Thread.current.object_id
|
14
34
|
|
15
|
-
|
16
|
-
|
17
|
-
|
35
|
+
self.request_method = request.request_method
|
36
|
+
self.normalized_path_info = normalized_path(request)
|
37
|
+
self.mime_type = request.headers['Content-Type']
|
38
|
+
self.headers = RequestHandler.selected_headers(request.env)
|
39
|
+
self.authorization = request.headers['Authorization']
|
40
|
+
self.path_info = request.path_info.split('?')[0]
|
18
41
|
# ActionDispatch::Http::ParameterFilter is deprecated
|
19
42
|
parameter_filter_cls = \
|
20
43
|
if defined?(ActiveSupport::ParameterFilter)
|
@@ -22,7 +45,7 @@ module AppMap
|
|
22
45
|
else
|
23
46
|
ActionDispatch::Http::ParameterFilter
|
24
47
|
end
|
25
|
-
|
48
|
+
self.params = parameter_filter_cls.new(::Rails.application.config.filter_parameters).filter(request.params)
|
26
49
|
end
|
27
50
|
|
28
51
|
def to_h
|
@@ -30,7 +53,10 @@ module AppMap
|
|
30
53
|
h[:http_server_request] = {
|
31
54
|
request_method: request_method,
|
32
55
|
path_info: path_info,
|
33
|
-
|
56
|
+
mime_type: mime_type,
|
57
|
+
normalized_path_info: normalized_path_info,
|
58
|
+
authorization: authorization,
|
59
|
+
headers: headers,
|
34
60
|
}.compact
|
35
61
|
|
36
62
|
h[:message] = params.keys.map do |key|
|
@@ -39,8 +65,11 @@ module AppMap
|
|
39
65
|
name: key,
|
40
66
|
class: val.class.name,
|
41
67
|
value: self.class.display_string(val),
|
42
|
-
object_id: val.__id__
|
43
|
-
}
|
68
|
+
object_id: val.__id__,
|
69
|
+
}.tap do |message|
|
70
|
+
properties = object_properties(val)
|
71
|
+
message[:properties] = properties if properties
|
72
|
+
end
|
44
73
|
end
|
45
74
|
end
|
46
75
|
end
|
@@ -59,7 +88,7 @@ module AppMap
|
|
59
88
|
end
|
60
89
|
|
61
90
|
class HTTPServerResponse < AppMap::Event::MethodReturnIgnoreValue
|
62
|
-
attr_accessor :status, :mime_type
|
91
|
+
attr_accessor :status, :mime_type, :headers
|
63
92
|
|
64
93
|
def initialize(response, parent_id, elapsed)
|
65
94
|
super AppMap::Event.next_id_counter, :return, Thread.current.object_id
|
@@ -68,13 +97,15 @@ module AppMap
|
|
68
97
|
self.mime_type = response.headers['Content-Type']
|
69
98
|
self.parent_id = parent_id
|
70
99
|
self.elapsed = elapsed
|
100
|
+
self.headers = RequestHandler.selected_headers(response.headers)
|
71
101
|
end
|
72
102
|
|
73
103
|
def to_h
|
74
104
|
super.tap do |h|
|
75
105
|
h[:http_server_response] = {
|
76
106
|
status: status,
|
77
|
-
mime_type: mime_type
|
107
|
+
mime_type: mime_type,
|
108
|
+
headers: headers
|
78
109
|
}.compact
|
79
110
|
end
|
80
111
|
end
|
data/lib/appmap/record.rb
CHANGED
data/lib/appmap/rspec.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'appmap/util'
|
4
|
+
require 'set'
|
4
5
|
|
5
6
|
module AppMap
|
6
7
|
# Integration of AppMap with RSpec. When enabled with APPMAP=true, the AppMap tracer will
|
@@ -13,58 +14,9 @@ module AppMap
|
|
13
14
|
AppMap.detect_metadata
|
14
15
|
end
|
15
16
|
|
16
|
-
module FeatureAnnotations
|
17
|
-
def feature
|
18
|
-
return nil unless annotations
|
19
|
-
|
20
|
-
annotations[:feature]
|
21
|
-
end
|
22
|
-
|
23
|
-
def labels
|
24
|
-
labels = metadata[:appmap]
|
25
|
-
if labels.is_a?(Array)
|
26
|
-
labels
|
27
|
-
elsif labels.is_a?(String) || labels.is_a?(Symbol)
|
28
|
-
[ labels ]
|
29
|
-
else
|
30
|
-
[]
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def feature_group
|
35
|
-
return nil unless annotations
|
36
|
-
|
37
|
-
annotations[:feature_group]
|
38
|
-
end
|
39
|
-
|
40
|
-
def annotations
|
41
|
-
metadata.tap do |md|
|
42
|
-
description_args_hashes.each do |h|
|
43
|
-
md.merge! h
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
protected
|
49
|
-
|
50
|
-
def metadata
|
51
|
-
return {} unless example_obj.respond_to?(:metadata)
|
52
|
-
|
53
|
-
example_obj.metadata
|
54
|
-
end
|
55
|
-
|
56
|
-
def description_args_hashes
|
57
|
-
return [] unless example_obj.respond_to?(:metadata)
|
58
|
-
|
59
|
-
(example_obj.metadata[:description_args] || []).select { |arg| arg.is_a?(Hash) }
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
17
|
# ScopeExample and ScopeExampleGroup is a way to handle the weird way that RSpec
|
64
18
|
# stores the nested example names.
|
65
19
|
ScopeExample = Struct.new(:example) do
|
66
|
-
include FeatureAnnotations
|
67
|
-
|
68
20
|
alias_method :example_obj, :example
|
69
21
|
|
70
22
|
def description?
|
@@ -83,8 +35,6 @@ module AppMap
|
|
83
35
|
# As you can see here, the way that RSpec stores the example description and
|
84
36
|
# represents the example group hierarchy is pretty weird.
|
85
37
|
ScopeExampleGroup = Struct.new(:example_group) do
|
86
|
-
include FeatureAnnotations
|
87
|
-
|
88
38
|
alias_method :example_obj, :example_group
|
89
39
|
|
90
40
|
def description_args
|
@@ -133,13 +83,20 @@ module AppMap
|
|
133
83
|
page.driver.options[:http_client].instance_variable_get('@server_url').port
|
134
84
|
end
|
135
85
|
|
136
|
-
warn "Starting recording of example #{example}" if AppMap::RSpec::LOG
|
86
|
+
warn "Starting recording of example #{example}@#{source_location}" if AppMap::RSpec::LOG
|
137
87
|
@trace = AppMap.tracing.trace
|
138
88
|
@webdriver_port = webdriver_port.()
|
139
89
|
end
|
140
90
|
|
141
|
-
def
|
91
|
+
def source_location
|
92
|
+
result = example.location_rerun_argument.split(':')[0]
|
93
|
+
result = result[2..-1] if result.index('./') == 0
|
94
|
+
result
|
95
|
+
end
|
96
|
+
|
97
|
+
def finish(exception)
|
142
98
|
warn "Finishing recording of example #{example}" if AppMap::RSpec::LOG
|
99
|
+
warn "Exception: #{exception}" if exception && AppMap::RSpec::LOG
|
143
100
|
|
144
101
|
events = []
|
145
102
|
AppMap.tracing.delete @trace
|
@@ -148,22 +105,16 @@ module AppMap
|
|
148
105
|
|
149
106
|
AppMap::RSpec.add_event_methods @trace.event_methods
|
150
107
|
|
151
|
-
class_map = AppMap.class_map(@trace.event_methods
|
108
|
+
class_map = AppMap.class_map(@trace.event_methods)
|
152
109
|
|
153
110
|
description = []
|
154
111
|
scope = ScopeExample.new(example)
|
155
|
-
feature_group = feature = nil
|
156
112
|
|
157
|
-
labels = []
|
158
113
|
while scope
|
159
|
-
labels += scope.labels
|
160
114
|
description << scope.description
|
161
|
-
feature ||= scope.feature
|
162
|
-
feature_group ||= scope.feature_group
|
163
115
|
scope = scope.parent
|
164
116
|
end
|
165
117
|
|
166
|
-
labels = labels.map(&:to_s).map(&:strip).reject(&:blank?).map(&:downcase).uniq
|
167
118
|
description.reject!(&:nil?).reject!(&:blank?)
|
168
119
|
default_description = description.last
|
169
120
|
description.reverse!
|
@@ -177,24 +128,12 @@ module AppMap
|
|
177
128
|
|
178
129
|
full_description = normalize.call(description.join(' '))
|
179
130
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
end
|
187
|
-
|
188
|
-
feature_group ||= normalize.call(default_description).underscore.gsub('/', '_').humanize
|
189
|
-
feature_name = feature || compute_feature_name.call if feature_group
|
190
|
-
feature_name = normalize.call(feature_name) if feature_name
|
191
|
-
|
192
|
-
AppMap::RSpec.save full_description,
|
193
|
-
class_map,
|
194
|
-
events: events,
|
195
|
-
feature_name: feature_name,
|
196
|
-
feature_group_name: feature_group,
|
197
|
-
labels: labels.blank? ? nil : labels
|
131
|
+
AppMap::RSpec.save name: full_description,
|
132
|
+
class_map: class_map,
|
133
|
+
source_location: source_location,
|
134
|
+
test_status: exception ? 'failed' : 'succeeded',
|
135
|
+
exception: exception,
|
136
|
+
events: events
|
198
137
|
end
|
199
138
|
end
|
200
139
|
|
@@ -212,11 +151,11 @@ module AppMap
|
|
212
151
|
@recordings_by_example[example.object_id] = Recording.new(example)
|
213
152
|
end
|
214
153
|
|
215
|
-
def end_spec(example)
|
154
|
+
def end_spec(example, exception:)
|
216
155
|
recording = @recordings_by_example.delete(example.object_id)
|
217
156
|
return warn "No recording found for #{example}" unless recording
|
218
157
|
|
219
|
-
recording.finish
|
158
|
+
recording.finish exception
|
220
159
|
end
|
221
160
|
|
222
161
|
def config
|
@@ -227,13 +166,11 @@ module AppMap
|
|
227
166
|
@event_methods += event_methods
|
228
167
|
end
|
229
168
|
|
230
|
-
def save(
|
169
|
+
def save(name:, class_map:, source_location:, test_status:, exception:, events:)
|
231
170
|
metadata = AppMap::RSpec.metadata.tap do |m|
|
232
|
-
m[:name] =
|
171
|
+
m[:name] = name
|
172
|
+
m[:source_location] = source_location
|
233
173
|
m[:app] = AppMap.configuration.name
|
234
|
-
m[:feature] = feature_name if feature_name
|
235
|
-
m[:feature_group] = feature_group_name if feature_group_name
|
236
|
-
m[:labels] = labels if labels
|
237
174
|
m[:frameworks] ||= []
|
238
175
|
m[:frameworks] << {
|
239
176
|
name: 'rspec',
|
@@ -242,6 +179,13 @@ module AppMap
|
|
242
179
|
m[:recorder] = {
|
243
180
|
name: 'rspec'
|
244
181
|
}
|
182
|
+
m[:test_status] = test_status
|
183
|
+
if exception
|
184
|
+
m[:exception] = {
|
185
|
+
class: exception.class.name,
|
186
|
+
message: exception.to_s
|
187
|
+
}
|
188
|
+
end
|
245
189
|
end
|
246
190
|
|
247
191
|
appmap = {
|
@@ -250,14 +194,9 @@ module AppMap
|
|
250
194
|
classMap: class_map,
|
251
195
|
events: events
|
252
196
|
}.compact
|
253
|
-
fname = AppMap::Util.scenario_filename(
|
254
|
-
|
255
|
-
File.write(File.join(APPMAP_OUTPUT_DIR, fname), JSON.generate(appmap))
|
256
|
-
end
|
197
|
+
fname = AppMap::Util.scenario_filename(name)
|
257
198
|
|
258
|
-
|
259
|
-
class_map = AppMap.class_map(@event_methods)
|
260
|
-
save 'Inventory', class_map, labels: %w[inventory]
|
199
|
+
AppMap::Util.write_appmap(File.join(APPMAP_OUTPUT_DIR, fname), JSON.generate(appmap))
|
261
200
|
end
|
262
201
|
|
263
202
|
def enabled?
|
@@ -266,9 +205,6 @@ module AppMap
|
|
266
205
|
|
267
206
|
def run
|
268
207
|
init
|
269
|
-
at_exit do
|
270
|
-
print_inventory
|
271
|
-
end
|
272
208
|
end
|
273
209
|
end
|
274
210
|
end
|
@@ -290,7 +226,7 @@ if AppMap::RSpec.enabled?
|
|
290
226
|
begin
|
291
227
|
instance_exec(&fn)
|
292
228
|
ensure
|
293
|
-
AppMap::RSpec.end_spec example
|
229
|
+
AppMap::RSpec.end_spec example, exception: $!
|
294
230
|
end
|
295
231
|
end
|
296
232
|
end
|