appmap 0.28.0 → 0.34.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/CHANGELOG.md +32 -0
- data/README.md +54 -2
- data/Rakefile +1 -1
- data/appmap.gemspec +1 -0
- data/lib/appmap.rb +25 -14
- data/lib/appmap/algorithm/stats.rb +2 -1
- data/lib/appmap/class_map.rb +26 -28
- data/lib/appmap/config.rb +115 -0
- data/lib/appmap/event.rb +28 -19
- data/lib/appmap/hook.rb +88 -129
- data/lib/appmap/hook/method.rb +78 -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 +17 -9
- data/lib/appmap/util.rb +19 -0
- data/lib/appmap/version.rb +1 -1
- data/package-lock.json +3 -3
- 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/singleton_method.rb +54 -0
- data/spec/hook_spec.rb +280 -53
- 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 +14 -4
- 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 +38 -4
- data/spec/fixtures/hook/class_method.rb +0 -17
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
|
@@ -67,7 +75,7 @@ module AppMap
|
|
67
75
|
return unless @enabled
|
68
76
|
|
69
77
|
@events << event
|
70
|
-
@methods << Trace::ScopedMethod.new(defined_class, method) if defined_class && method
|
78
|
+
@methods << Trace::ScopedMethod.new(defined_class, method, event.static) if (defined_class && method && event.event == :call)
|
71
79
|
end
|
72
80
|
|
73
81
|
# Gets a unique list of the methods that were invoked by the program.
|
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
data/package-lock.json
CHANGED
@@ -551,9 +551,9 @@
|
|
551
551
|
}
|
552
552
|
},
|
553
553
|
"lodash": {
|
554
|
-
"version": "4.17.
|
555
|
-
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.
|
556
|
-
"integrity": "sha512-
|
554
|
+
"version": "4.17.19",
|
555
|
+
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
556
|
+
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
|
557
557
|
},
|
558
558
|
"longest": {
|
559
559
|
"version": "1.0.1",
|
@@ -48,12 +48,12 @@ 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
56
|
static: false
|
56
|
-
thread_id: .*
|
57
57
|
parameters:
|
58
58
|
- name: params
|
59
59
|
class: Hash
|
@@ -47,18 +47,18 @@ describe 'AbstractControllerBase' do
|
|
47
47
|
SERVER_REQUEST
|
48
48
|
end
|
49
49
|
|
50
|
-
it '
|
50
|
+
it 'properly captures method parameters in the appmap' do
|
51
51
|
expect(File).to exist(appmap_json)
|
52
52
|
appmap = JSON.parse(File.read(appmap_json)).to_yaml
|
53
53
|
|
54
54
|
expect(appmap).to match(<<-CREATE_CALL.strip)
|
55
55
|
event: call
|
56
|
+
thread_id: .*
|
56
57
|
defined_class: Api::UsersController
|
57
58
|
method_id: build_user
|
58
59
|
path: app/controllers/api/users_controller.rb
|
59
60
|
lineno: 23
|
60
61
|
static: false
|
61
|
-
thread_id: .*
|
62
62
|
parameters:
|
63
63
|
- name: params
|
64
64
|
class: ActiveSupport::HashWithIndifferentAccess
|
@@ -68,5 +68,12 @@ describe 'AbstractControllerBase' do
|
|
68
68
|
receiver:
|
69
69
|
CREATE_CALL
|
70
70
|
end
|
71
|
+
|
72
|
+
it 'returns a minimal event' do
|
73
|
+
expect(File).to exist(appmap_json)
|
74
|
+
appmap = JSON.parse(File.read(appmap_json))
|
75
|
+
event = appmap['events'].find { |event| event['event'] == 'return' && event['return_value'] }
|
76
|
+
expect(event.keys).to eq(%w[id event thread_id parent_id elapsed return_value])
|
77
|
+
end
|
71
78
|
end
|
72
79
|
end
|
data/spec/config_spec.rb
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'rails_spec_helper'
|
4
4
|
require 'active_support/core_ext'
|
5
|
-
require 'appmap/
|
5
|
+
require 'appmap/config'
|
6
6
|
|
7
|
-
describe AppMap::
|
7
|
+
describe AppMap::Config, docker: false do
|
8
8
|
it 'loads from a Hash' do
|
9
9
|
config_data = {
|
10
10
|
name: 'test',
|
@@ -18,7 +18,7 @@ describe AppMap::Hook::Config do
|
|
18
18
|
}
|
19
19
|
]
|
20
20
|
}.deep_stringify_keys!
|
21
|
-
config = AppMap::
|
21
|
+
config = AppMap::Config.load(config_data)
|
22
22
|
|
23
23
|
expect(config.to_h.deep_stringify_keys!).to eq(config_data)
|
24
24
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class SingletonMethod
|
4
|
+
class << self
|
5
|
+
def say_default
|
6
|
+
'default'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def SingletonMethod.say_class_defined
|
11
|
+
'defined with explicit class scope'
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.say_self_defined
|
15
|
+
'defined with self class scope'
|
16
|
+
end
|
17
|
+
|
18
|
+
# When called, do_include calls +include+ to bring in the module
|
19
|
+
# AddMethod. AddMethod defines a new instance method, which gets
|
20
|
+
# added to the singleton class of SingletonMethod.
|
21
|
+
def do_include
|
22
|
+
class << self
|
23
|
+
SingletonMethod.include(AddMethod)
|
24
|
+
end
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.new_with_instance_method
|
29
|
+
SingletonMethod.new.tap do |m|
|
30
|
+
def m.say_instance_defined
|
31
|
+
'defined for an instance'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
'Singleton Method fixture'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module AddMethod
|
42
|
+
def self.included(base)
|
43
|
+
base.module_eval do
|
44
|
+
define_method "added_method" do
|
45
|
+
_added_method
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def _added_method
|
51
|
+
'defined by including a module'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|