appmap 0.28.1 → 0.34.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -1
- data/README.md +54 -2
- data/Rakefile +1 -1
- data/appmap.gemspec +2 -0
- data/lib/appmap.rb +25 -14
- data/lib/appmap/class_map.rb +25 -27
- data/lib/appmap/config.rb +115 -0
- data/lib/appmap/cucumber.rb +19 -2
- data/lib/appmap/event.rb +25 -16
- data/lib/appmap/hook.rb +89 -139
- data/lib/appmap/hook/method.rb +83 -0
- data/lib/appmap/metadata.rb +1 -1
- data/lib/appmap/minitest.rb +141 -0
- data/lib/appmap/open.rb +57 -0
- data/lib/appmap/rails/action_handler.rb +7 -7
- data/lib/appmap/rails/sql_handler.rb +10 -8
- data/lib/appmap/record.rb +27 -0
- data/lib/appmap/rspec.rb +2 -2
- data/lib/appmap/trace.rb +16 -8
- data/lib/appmap/util.rb +19 -0
- data/lib/appmap/version.rb +1 -1
- data/spec/abstract_controller4_base_spec.rb +1 -1
- data/spec/abstract_controller_base_spec.rb +9 -2
- data/spec/config_spec.rb +3 -3
- data/spec/fixtures/hook/compare.rb +7 -0
- data/spec/fixtures/hook/instance_method.rb +4 -0
- data/spec/hook_spec.rb +222 -37
- data/spec/open_spec.rb +19 -0
- data/spec/record_sql_rails_pg_spec.rb +56 -33
- data/spec/util_spec.rb +1 -1
- data/test/cli_test.rb +12 -2
- data/test/fixtures/minitest_recorder/Gemfile +5 -0
- data/test/fixtures/minitest_recorder/appmap.yml +3 -0
- data/test/fixtures/minitest_recorder/lib/hello.rb +5 -0
- data/test/fixtures/minitest_recorder/test/hello_test.rb +12 -0
- data/test/fixtures/openssl_recorder/Gemfile +3 -0
- data/test/fixtures/openssl_recorder/appmap.yml +3 -0
- data/test/fixtures/openssl_recorder/lib/openssl_cert_sign.rb +94 -0
- data/test/fixtures/openssl_recorder/lib/openssl_encrypt.rb +34 -0
- data/test/fixtures/openssl_recorder/lib/openssl_key_sign.rb +28 -0
- data/test/fixtures/process_recorder/appmap.yml +3 -0
- data/test/fixtures/process_recorder/hello.rb +9 -0
- data/test/minitest_test.rb +38 -0
- data/test/openssl_test.rb +203 -0
- data/test/record_process_test.rb +35 -0
- data/test/test_helper.rb +1 -0
- metadata +51 -2
data/lib/appmap/metadata.rb
CHANGED
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'appmap/util'
|
4
|
+
|
5
|
+
module AppMap
|
6
|
+
# Integration of AppMap with Minitest. When enabled with APPMAP=true, the AppMap tracer will
|
7
|
+
# be activated around each test.
|
8
|
+
module Minitest
|
9
|
+
APPMAP_OUTPUT_DIR = 'tmp/appmap/minitest'
|
10
|
+
LOG = false
|
11
|
+
|
12
|
+
def self.metadata
|
13
|
+
AppMap.detect_metadata
|
14
|
+
end
|
15
|
+
|
16
|
+
Recording = Struct.new(:test) do
|
17
|
+
def initialize(test)
|
18
|
+
super
|
19
|
+
|
20
|
+
warn "Starting recording of test #{test.class}.#{test.name}" if AppMap::Minitest::LOG
|
21
|
+
@trace = AppMap.tracing.trace
|
22
|
+
end
|
23
|
+
|
24
|
+
def finish
|
25
|
+
warn "Finishing recording of test #{test.class}.#{test.name}" if AppMap::Minitest::LOG
|
26
|
+
|
27
|
+
events = []
|
28
|
+
AppMap.tracing.delete @trace
|
29
|
+
|
30
|
+
events << @trace.next_event.to_h while @trace.event?
|
31
|
+
|
32
|
+
AppMap::Minitest.add_event_methods @trace.event_methods
|
33
|
+
|
34
|
+
class_map = AppMap.class_map(@trace.event_methods)
|
35
|
+
|
36
|
+
feature_group = test.class.name.underscore.split('_')[0...-1].join('_').capitalize
|
37
|
+
feature_name = test.name.split('_')[1..-1].join(' ')
|
38
|
+
scenario_name = [ feature_group, feature_name ].join(' ')
|
39
|
+
|
40
|
+
AppMap::Minitest.save scenario_name,
|
41
|
+
class_map,
|
42
|
+
events: events,
|
43
|
+
feature_name: feature_name,
|
44
|
+
feature_group_name: feature_group
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
@recordings_by_test = {}
|
49
|
+
@event_methods = Set.new
|
50
|
+
|
51
|
+
class << self
|
52
|
+
def init
|
53
|
+
warn 'Configuring AppMap recorder for Minitest'
|
54
|
+
|
55
|
+
FileUtils.mkdir_p APPMAP_OUTPUT_DIR
|
56
|
+
end
|
57
|
+
|
58
|
+
def begin_test(test)
|
59
|
+
@recordings_by_test[test.object_id] = Recording.new(test)
|
60
|
+
end
|
61
|
+
|
62
|
+
def end_test(test)
|
63
|
+
recording = @recordings_by_test.delete(test.object_id)
|
64
|
+
return warn "No recording found for #{test}" unless recording
|
65
|
+
|
66
|
+
recording.finish
|
67
|
+
end
|
68
|
+
|
69
|
+
def config
|
70
|
+
@config or raise "AppMap is not configured"
|
71
|
+
end
|
72
|
+
|
73
|
+
def add_event_methods(event_methods)
|
74
|
+
@event_methods += event_methods
|
75
|
+
end
|
76
|
+
|
77
|
+
def save(example_name, class_map, events: nil, feature_name: nil, feature_group_name: nil, labels: nil)
|
78
|
+
metadata = AppMap::Minitest.metadata.tap do |m|
|
79
|
+
m[:name] = example_name
|
80
|
+
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
|
+
m[:frameworks] ||= []
|
84
|
+
m[:frameworks] << {
|
85
|
+
name: 'minitest',
|
86
|
+
version: Gem.loaded_specs['minitest']&.version&.to_s
|
87
|
+
}
|
88
|
+
m[:recorder] = {
|
89
|
+
name: 'minitest'
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
appmap = {
|
94
|
+
version: AppMap::APPMAP_FORMAT_VERSION,
|
95
|
+
metadata: metadata,
|
96
|
+
classMap: class_map,
|
97
|
+
events: events
|
98
|
+
}.compact
|
99
|
+
fname = AppMap::Util.scenario_filename(example_name)
|
100
|
+
|
101
|
+
File.write(File.join(APPMAP_OUTPUT_DIR, fname), JSON.generate(appmap))
|
102
|
+
end
|
103
|
+
|
104
|
+
def print_inventory
|
105
|
+
class_map = AppMap.class_map(@event_methods)
|
106
|
+
save 'Inventory', class_map, labels: %w[inventory]
|
107
|
+
end
|
108
|
+
|
109
|
+
def enabled?
|
110
|
+
ENV['APPMAP'] == 'true'
|
111
|
+
end
|
112
|
+
|
113
|
+
def run
|
114
|
+
init
|
115
|
+
at_exit do
|
116
|
+
print_inventory
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
if AppMap::Minitest.enabled?
|
124
|
+
require 'appmap'
|
125
|
+
require 'minitest/test'
|
126
|
+
|
127
|
+
class ::Minitest::Test
|
128
|
+
alias run_without_hook run
|
129
|
+
|
130
|
+
def run
|
131
|
+
AppMap::Minitest.begin_test self
|
132
|
+
begin
|
133
|
+
run_without_hook
|
134
|
+
ensure
|
135
|
+
AppMap::Minitest.end_test self
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
AppMap::Minitest.run
|
141
|
+
end
|
data/lib/appmap/open.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppMap
|
4
|
+
OpenStruct = Struct.new(:appmap)
|
5
|
+
|
6
|
+
class Open < OpenStruct
|
7
|
+
attr_reader :port
|
8
|
+
|
9
|
+
def perform
|
10
|
+
server = run_server
|
11
|
+
open_browser
|
12
|
+
server.kill
|
13
|
+
end
|
14
|
+
|
15
|
+
def page
|
16
|
+
require 'rack/utils'
|
17
|
+
<<~PAGE
|
18
|
+
<!DOCTYPE html>
|
19
|
+
<html>
|
20
|
+
<head>
|
21
|
+
<title>…</title>
|
22
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
23
|
+
<script type="text/javascript">
|
24
|
+
function dosubmit() { document.forms[0].submit(); }
|
25
|
+
</script>
|
26
|
+
</head>
|
27
|
+
<body onload="dosubmit();">
|
28
|
+
<form action="https://app.land/scenario_uploads" method="POST" accept-charset="utf-8">
|
29
|
+
<input type="hidden" name="data" value='#{Rack::Utils.escape_html appmap.to_json}'>
|
30
|
+
</form>
|
31
|
+
</body>
|
32
|
+
</html>
|
33
|
+
PAGE
|
34
|
+
end
|
35
|
+
|
36
|
+
def run_server
|
37
|
+
require 'rack'
|
38
|
+
Thread.new do
|
39
|
+
Rack::Handler::WEBrick.run(
|
40
|
+
lambda do |env|
|
41
|
+
return [200, { 'Content-Type' => 'text/html' }, [page]]
|
42
|
+
end,
|
43
|
+
:Port => 0
|
44
|
+
) do |server|
|
45
|
+
@port = server.config[:Port]
|
46
|
+
end
|
47
|
+
end.tap do
|
48
|
+
sleep 1.0
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def open_browser
|
53
|
+
system 'open', "http://localhost:#{@port}"
|
54
|
+
sleep 5.0
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -16,11 +16,11 @@ module AppMap
|
|
16
16
|
class HTTPServerRequest
|
17
17
|
include ContextKey
|
18
18
|
|
19
|
-
class Call < AppMap::Event::
|
19
|
+
class Call < AppMap::Event::MethodCall
|
20
20
|
attr_accessor :payload
|
21
21
|
|
22
|
-
def initialize(
|
23
|
-
super AppMap::Event.next_id_counter, :call,
|
22
|
+
def initialize(payload)
|
23
|
+
super AppMap::Event.next_id_counter, :call, Thread.current.object_id
|
24
24
|
|
25
25
|
self.payload = payload
|
26
26
|
end
|
@@ -47,7 +47,7 @@ module AppMap
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def call(_, started, finished, _, payload) # (name, started, finished, unique_id, payload)
|
50
|
-
event = Call.new(
|
50
|
+
event = Call.new(payload)
|
51
51
|
Thread.current[context_key] = Context.new(event.id, Time.now)
|
52
52
|
AppMap.tracing.record_event(event)
|
53
53
|
end
|
@@ -59,8 +59,8 @@ module AppMap
|
|
59
59
|
class Call < AppMap::Event::MethodReturnIgnoreValue
|
60
60
|
attr_accessor :payload
|
61
61
|
|
62
|
-
def initialize(
|
63
|
-
super AppMap::Event.next_id_counter, :return,
|
62
|
+
def initialize(payload, parent_id, elapsed)
|
63
|
+
super AppMap::Event.next_id_counter, :return, Thread.current.object_id
|
64
64
|
|
65
65
|
self.payload = payload
|
66
66
|
self.parent_id = parent_id
|
@@ -82,7 +82,7 @@ module AppMap
|
|
82
82
|
context = Thread.current[context_key]
|
83
83
|
Thread.current[context_key] = nil
|
84
84
|
|
85
|
-
event = Call.new(
|
85
|
+
event = Call.new(payload, context.id, Time.now - context.start_time)
|
86
86
|
AppMap.tracing.record_event(event)
|
87
87
|
end
|
88
88
|
end
|
@@ -5,11 +5,11 @@ require 'appmap/event'
|
|
5
5
|
module AppMap
|
6
6
|
module Rails
|
7
7
|
class SQLHandler
|
8
|
-
class SQLCall < AppMap::Event::
|
8
|
+
class SQLCall < AppMap::Event::MethodCall
|
9
9
|
attr_accessor :payload
|
10
10
|
|
11
|
-
def initialize(
|
12
|
-
super AppMap::Event.next_id_counter, :call,
|
11
|
+
def initialize(payload)
|
12
|
+
super AppMap::Event.next_id_counter, :call, Thread.current.object_id
|
13
13
|
|
14
14
|
self.payload = payload
|
15
15
|
end
|
@@ -20,7 +20,7 @@ module AppMap
|
|
20
20
|
sql: payload[:sql],
|
21
21
|
database_type: payload[:database_type]
|
22
22
|
}.tap do |sql_query|
|
23
|
-
%i[server_version
|
23
|
+
%i[server_version].each do |attribute|
|
24
24
|
sql_query[attribute] = payload[attribute] if payload[attribute]
|
25
25
|
end
|
26
26
|
end
|
@@ -29,8 +29,8 @@ module AppMap
|
|
29
29
|
end
|
30
30
|
|
31
31
|
class SQLReturn < AppMap::Event::MethodReturnIgnoreValue
|
32
|
-
def initialize(
|
33
|
-
super AppMap::Event.next_id_counter, :return,
|
32
|
+
def initialize(parent_id, elapsed)
|
33
|
+
super AppMap::Event.next_id_counter, :return, Thread.current.object_id
|
34
34
|
|
35
35
|
self.parent_id = parent_id
|
36
36
|
self.elapsed = elapsed
|
@@ -76,6 +76,8 @@ module AppMap
|
|
76
76
|
case database_type
|
77
77
|
when :postgres
|
78
78
|
ActiveRecord::Base.connection.postgresql_version
|
79
|
+
when :sqlite
|
80
|
+
ActiveRecord::Base.connection.database_version.to_s
|
79
81
|
else
|
80
82
|
warn "Unable to determine database version for #{database_type.inspect}"
|
81
83
|
end
|
@@ -133,9 +135,9 @@ module AppMap
|
|
133
135
|
|
134
136
|
SQLExaminer.examine payload, sql: sql
|
135
137
|
|
136
|
-
call = SQLCall.new(
|
138
|
+
call = SQLCall.new(payload)
|
137
139
|
AppMap.tracing.record_event(call)
|
138
|
-
AppMap.tracing.record_event(SQLReturn.new(
|
140
|
+
AppMap.tracing.record_event(SQLReturn.new(call.id, finished - started))
|
139
141
|
ensure
|
140
142
|
Thread.current[reentry_key] = nil
|
141
143
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'appmap'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
tracer = AppMap.tracing.trace
|
7
|
+
|
8
|
+
at_exit do
|
9
|
+
AppMap.tracing.delete(tracer)
|
10
|
+
|
11
|
+
events = [].tap do |event_list|
|
12
|
+
event_list << tracer.next_event.to_h while tracer.event?
|
13
|
+
end
|
14
|
+
|
15
|
+
metadata = AppMap.detect_metadata
|
16
|
+
metadata[:recorder] = {
|
17
|
+
name: 'record_process'
|
18
|
+
}
|
19
|
+
|
20
|
+
appmap = {
|
21
|
+
'version' => AppMap::APPMAP_FORMAT_VERSION,
|
22
|
+
'metadata' => metadata,
|
23
|
+
'classMap' => AppMap.class_map(tracer.event_methods),
|
24
|
+
'events' => events
|
25
|
+
}
|
26
|
+
File.write 'appmap.json', JSON.generate(appmap)
|
27
|
+
end
|
data/lib/appmap/rspec.rb
CHANGED
@@ -154,7 +154,7 @@ module AppMap
|
|
154
154
|
end
|
155
155
|
|
156
156
|
labels = labels.map(&:to_s).map(&:strip).reject(&:blank?).map(&:downcase).uniq
|
157
|
-
description.reject!(&:nil?).reject(&:blank?)
|
157
|
+
description.reject!(&:nil?).reject!(&:blank?)
|
158
158
|
default_description = description.last
|
159
159
|
description.reverse!
|
160
160
|
|
@@ -218,7 +218,7 @@ module AppMap
|
|
218
218
|
end
|
219
219
|
|
220
220
|
def save(example_name, class_map, events: nil, feature_name: nil, feature_group_name: nil, labels: nil)
|
221
|
-
metadata = RSpec.metadata.tap do |m|
|
221
|
+
metadata = AppMap::RSpec.metadata.tap do |m|
|
222
222
|
m[:name] = example_name
|
223
223
|
m[:app] = AppMap.configuration.name
|
224
224
|
m[:feature] = feature_name if feature_name
|
data/lib/appmap/trace.rb
CHANGED
@@ -2,38 +2,46 @@
|
|
2
2
|
|
3
3
|
module AppMap
|
4
4
|
module Trace
|
5
|
-
ScopedMethod
|
5
|
+
class ScopedMethod < SimpleDelegator
|
6
|
+
attr_reader :defined_class, :static
|
7
|
+
|
8
|
+
def initialize(defined_class, method, static)
|
9
|
+
@defined_class = defined_class
|
10
|
+
@static = static
|
11
|
+
super(method)
|
12
|
+
end
|
13
|
+
end
|
6
14
|
|
7
15
|
class Tracing
|
8
16
|
def initialize
|
9
|
-
@
|
17
|
+
@tracing = []
|
10
18
|
end
|
11
19
|
|
12
20
|
def empty?
|
13
|
-
@
|
21
|
+
@tracing.empty?
|
14
22
|
end
|
15
23
|
|
16
24
|
def trace(enable: true)
|
17
25
|
Tracer.new.tap do |tracer|
|
18
|
-
@
|
26
|
+
@tracing << tracer
|
19
27
|
tracer.enable if enable
|
20
28
|
end
|
21
29
|
end
|
22
30
|
|
23
31
|
def enabled?
|
24
|
-
@
|
32
|
+
@tracing.any?(&:enabled?)
|
25
33
|
end
|
26
34
|
|
27
35
|
def record_event(event, defined_class: nil, method: nil)
|
28
|
-
@
|
36
|
+
@tracing.each do |tracer|
|
29
37
|
tracer.record_event(event, defined_class: defined_class, method: method)
|
30
38
|
end
|
31
39
|
end
|
32
40
|
|
33
41
|
def delete(tracer)
|
34
|
-
return unless @
|
42
|
+
return unless @tracing.member?(tracer)
|
35
43
|
|
36
|
-
@
|
44
|
+
@tracing.delete(tracer)
|
37
45
|
tracer.disable
|
38
46
|
end
|
39
47
|
end
|
data/lib/appmap/util.rb
CHANGED
@@ -35,6 +35,25 @@ module AppMap
|
|
35
35
|
|
36
36
|
[ fname, extension ].join
|
37
37
|
end
|
38
|
+
|
39
|
+
# sanitize_event removes ephemeral values from an event, making
|
40
|
+
# events easier to compare across runs.
|
41
|
+
def sanitize_event(event, &block)
|
42
|
+
event.delete(:thread_id)
|
43
|
+
event.delete(:elapsed)
|
44
|
+
delete_object_id = ->(obj) { (obj || {}).delete(:object_id) }
|
45
|
+
delete_object_id.call(event[:receiver])
|
46
|
+
delete_object_id.call(event[:return_value])
|
47
|
+
(event[:parameters] || []).each(&delete_object_id)
|
48
|
+
(event[:exceptions] || []).each(&delete_object_id)
|
49
|
+
|
50
|
+
case event[:event]
|
51
|
+
when :call
|
52
|
+
event[:path] = event[:path].gsub(Gem.dir + '/', '')
|
53
|
+
end
|
54
|
+
|
55
|
+
event
|
56
|
+
end
|
38
57
|
end
|
39
58
|
end
|
40
59
|
end
|
data/lib/appmap/version.rb
CHANGED
@@ -48,11 +48,11 @@ describe 'AbstractControllerBase' do
|
|
48
48
|
|
49
49
|
expect(appmap).to match(<<-CREATE_CALL.strip)
|
50
50
|
event: call
|
51
|
+
thread_id: .*
|
51
52
|
defined_class: Api::UsersController
|
52
53
|
method_id: build_user
|
53
54
|
path: app/controllers/api/users_controller.rb
|
54
55
|
lineno: 23
|
55
|
-
thread_id: .*
|
56
56
|
static: false
|
57
57
|
parameters:
|
58
58
|
- name: params
|