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/spec/hook_spec.rb
CHANGED
@@ -5,54 +5,54 @@ require 'appmap/hook'
|
|
5
5
|
require 'appmap/event'
|
6
6
|
require 'diffy'
|
7
7
|
|
8
|
-
|
8
|
+
# Show nulls as the literal +null+, rather than just leaving the field
|
9
|
+
# empty. This make some of the expected YAML below easier to
|
10
|
+
# understand.
|
11
|
+
module ShowYamlNulls
|
12
|
+
def visit_NilClass(o)
|
13
|
+
@emitter.scalar('null', nil, 'tag:yaml.org,2002:null', true, false, Psych::Nodes::Scalar::ANY)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
Psych::Visitors::YAMLTree.prepend(ShowYamlNulls)
|
17
|
+
|
18
|
+
describe 'AppMap class Hooking', docker: false do
|
19
|
+
require 'appmap/util'
|
9
20
|
def collect_events(tracer)
|
10
21
|
[].tap do |events|
|
11
22
|
while tracer.event?
|
12
23
|
events << tracer.next_event.to_h
|
13
24
|
end
|
14
|
-
end.map
|
15
|
-
event.delete(:thread_id)
|
16
|
-
event.delete(:elapsed)
|
17
|
-
delete_object_id = ->(obj) { (obj || {}).delete(:object_id) }
|
18
|
-
delete_object_id.call(event[:receiver])
|
19
|
-
delete_object_id.call(event[:return_value])
|
20
|
-
(event[:parameters] || []).each(&delete_object_id)
|
21
|
-
(event[:exceptions] || []).each(&delete_object_id)
|
22
|
-
|
23
|
-
if event[:event] == :return
|
24
|
-
# These should be removed from the appmap spec
|
25
|
-
%i[defined_class method_id path lineno static].each do |obsolete_field|
|
26
|
-
event.delete(obsolete_field)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
event
|
30
|
-
end.to_yaml
|
25
|
+
end.map(&AppMap::Util.method(:sanitize_event)).to_yaml
|
31
26
|
end
|
32
27
|
|
33
|
-
def invoke_test_file(file, &block)
|
28
|
+
def invoke_test_file(file, setup: nil, &block)
|
34
29
|
AppMap.configuration = nil
|
35
|
-
package = AppMap::
|
36
|
-
config = AppMap::
|
30
|
+
package = AppMap::Config::Package.new(file)
|
31
|
+
config = AppMap::Config.new('hook_spec', [ package ])
|
37
32
|
AppMap.configuration = config
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
33
|
+
tracer = nil
|
34
|
+
AppMap::Hook.new(config).enable do
|
35
|
+
setup_result = setup.call if setup
|
36
|
+
|
37
|
+
tracer = AppMap.tracing.trace
|
38
|
+
AppMap::Event.reset_id_counter
|
39
|
+
begin
|
40
|
+
load file
|
41
|
+
yield setup_result
|
42
|
+
ensure
|
43
|
+
AppMap.tracing.delete(tracer)
|
44
|
+
end
|
47
45
|
end
|
46
|
+
|
48
47
|
[ config, tracer ]
|
49
48
|
end
|
50
49
|
|
51
|
-
def test_hook_behavior(file, events_yaml, &block)
|
52
|
-
config, tracer = invoke_test_file(file, &block)
|
50
|
+
def test_hook_behavior(file, events_yaml, setup: nil, &block)
|
51
|
+
config, tracer = invoke_test_file(file, setup: setup, &block)
|
53
52
|
|
54
53
|
events = collect_events(tracer)
|
55
|
-
|
54
|
+
|
55
|
+
expect(Diffy::Diff.new(events_yaml, events).to_s).to eq('')
|
56
56
|
|
57
57
|
[ config, tracer ]
|
58
58
|
end
|
@@ -82,7 +82,7 @@ describe 'AppMap class Hooking' do
|
|
82
82
|
:class: String
|
83
83
|
:value: default
|
84
84
|
YAML
|
85
|
-
|
85
|
+
test_hook_behavior 'spec/fixtures/hook/instance_method.rb', events_yaml do
|
86
86
|
expect(InstanceMethod.new.say_default).to eq('default')
|
87
87
|
end
|
88
88
|
end
|
@@ -92,7 +92,7 @@ describe 'AppMap class Hooking' do
|
|
92
92
|
InstanceMethod.new.say_default
|
93
93
|
end
|
94
94
|
expect(tracer.event_methods.to_a.map(&:defined_class)).to eq([ 'InstanceMethod' ])
|
95
|
-
expect(tracer.event_methods.to_a.map(&:
|
95
|
+
expect(tracer.event_methods.to_a.map(&:to_s)).to eq([ InstanceMethod.public_instance_method(:say_default).to_s ])
|
96
96
|
end
|
97
97
|
|
98
98
|
it 'builds a class map of invoked methods' do
|
@@ -100,7 +100,7 @@ describe 'AppMap class Hooking' do
|
|
100
100
|
InstanceMethod.new.say_default
|
101
101
|
end
|
102
102
|
class_map = AppMap.class_map(tracer.event_methods).to_yaml
|
103
|
-
expect(Diffy::Diff.new(
|
103
|
+
expect(Diffy::Diff.new(<<~YAML, class_map).to_s).to eq('')
|
104
104
|
---
|
105
105
|
- :name: spec/fixtures/hook/instance_method.rb
|
106
106
|
:type: package
|
@@ -208,7 +208,7 @@ describe 'AppMap class Hooking' do
|
|
208
208
|
:parameters:
|
209
209
|
- :name: :kw
|
210
210
|
:class: NilClass
|
211
|
-
:value:
|
211
|
+
:value: null
|
212
212
|
:kind: :key
|
213
213
|
:receiver:
|
214
214
|
:class: InstanceMethod
|
@@ -238,7 +238,7 @@ describe 'AppMap class Hooking' do
|
|
238
238
|
:parameters:
|
239
239
|
- :name: :block
|
240
240
|
:class: NilClass
|
241
|
-
:value:
|
241
|
+
:value: null
|
242
242
|
:kind: :block
|
243
243
|
:receiver:
|
244
244
|
:class: InstanceMethod
|
@@ -260,15 +260,15 @@ describe 'AppMap class Hooking' do
|
|
260
260
|
---
|
261
261
|
- :id: 1
|
262
262
|
:event: :call
|
263
|
-
:defined_class:
|
263
|
+
:defined_class: SingletonMethod
|
264
264
|
:method_id: say_default
|
265
|
-
:path: spec/fixtures/hook/
|
265
|
+
:path: spec/fixtures/hook/singleton_method.rb
|
266
266
|
:lineno: 5
|
267
267
|
:static: true
|
268
268
|
:parameters: []
|
269
269
|
:receiver:
|
270
270
|
:class: Class
|
271
|
-
:value:
|
271
|
+
:value: SingletonMethod
|
272
272
|
- :id: 2
|
273
273
|
:event: :return
|
274
274
|
:parent_id: 1
|
@@ -276,8 +276,8 @@ describe 'AppMap class Hooking' do
|
|
276
276
|
:class: String
|
277
277
|
:value: default
|
278
278
|
YAML
|
279
|
-
test_hook_behavior 'spec/fixtures/hook/
|
280
|
-
expect(
|
279
|
+
test_hook_behavior 'spec/fixtures/hook/singleton_method.rb', events_yaml do
|
280
|
+
expect(SingletonMethod.say_default).to eq('default')
|
281
281
|
end
|
282
282
|
end
|
283
283
|
|
@@ -286,15 +286,15 @@ describe 'AppMap class Hooking' do
|
|
286
286
|
---
|
287
287
|
- :id: 1
|
288
288
|
:event: :call
|
289
|
-
:defined_class:
|
289
|
+
:defined_class: SingletonMethod
|
290
290
|
:method_id: say_class_defined
|
291
|
-
:path: spec/fixtures/hook/
|
291
|
+
:path: spec/fixtures/hook/singleton_method.rb
|
292
292
|
:lineno: 10
|
293
293
|
:static: true
|
294
294
|
:parameters: []
|
295
295
|
:receiver:
|
296
296
|
:class: Class
|
297
|
-
:value:
|
297
|
+
:value: SingletonMethod
|
298
298
|
- :id: 2
|
299
299
|
:event: :return
|
300
300
|
:parent_id: 1
|
@@ -302,8 +302,8 @@ describe 'AppMap class Hooking' do
|
|
302
302
|
:class: String
|
303
303
|
:value: defined with explicit class scope
|
304
304
|
YAML
|
305
|
-
test_hook_behavior 'spec/fixtures/hook/
|
306
|
-
expect(
|
305
|
+
test_hook_behavior 'spec/fixtures/hook/singleton_method.rb', events_yaml do
|
306
|
+
expect(SingletonMethod.say_class_defined).to eq('defined with explicit class scope')
|
307
307
|
end
|
308
308
|
end
|
309
309
|
|
@@ -312,15 +312,15 @@ describe 'AppMap class Hooking' do
|
|
312
312
|
---
|
313
313
|
- :id: 1
|
314
314
|
:event: :call
|
315
|
-
:defined_class:
|
315
|
+
:defined_class: SingletonMethod
|
316
316
|
:method_id: say_self_defined
|
317
|
-
:path: spec/fixtures/hook/
|
317
|
+
:path: spec/fixtures/hook/singleton_method.rb
|
318
318
|
:lineno: 14
|
319
319
|
:static: true
|
320
320
|
:parameters: []
|
321
321
|
:receiver:
|
322
322
|
:class: Class
|
323
|
-
:value:
|
323
|
+
:value: SingletonMethod
|
324
324
|
- :id: 2
|
325
325
|
:event: :return
|
326
326
|
:parent_id: 1
|
@@ -328,8 +328,74 @@ describe 'AppMap class Hooking' do
|
|
328
328
|
:class: String
|
329
329
|
:value: defined with self class scope
|
330
330
|
YAML
|
331
|
-
test_hook_behavior 'spec/fixtures/hook/
|
332
|
-
expect(
|
331
|
+
test_hook_behavior 'spec/fixtures/hook/singleton_method.rb', events_yaml do
|
332
|
+
expect(SingletonMethod.say_self_defined).to eq('defined with self class scope')
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
|
337
|
+
it 'hooks an included method' do
|
338
|
+
events_yaml = <<~YAML
|
339
|
+
---
|
340
|
+
- :id: 1
|
341
|
+
:event: :call
|
342
|
+
:defined_class: SingletonMethod
|
343
|
+
:method_id: added_method
|
344
|
+
:path: spec/fixtures/hook/singleton_method.rb
|
345
|
+
:lineno: 44
|
346
|
+
:static: false
|
347
|
+
:parameters: []
|
348
|
+
:receiver:
|
349
|
+
:class: SingletonMethod
|
350
|
+
:value: Singleton Method fixture
|
351
|
+
- :id: 2
|
352
|
+
:event: :call
|
353
|
+
:defined_class: AddMethod
|
354
|
+
:method_id: _added_method
|
355
|
+
:path: spec/fixtures/hook/singleton_method.rb
|
356
|
+
:lineno: 50
|
357
|
+
:static: false
|
358
|
+
:parameters: []
|
359
|
+
:receiver:
|
360
|
+
:class: SingletonMethod
|
361
|
+
:value: Singleton Method fixture
|
362
|
+
- :id: 3
|
363
|
+
:event: :return
|
364
|
+
:parent_id: 2
|
365
|
+
:return_value:
|
366
|
+
:class: String
|
367
|
+
:value: defined by including a module
|
368
|
+
- :id: 4
|
369
|
+
:event: :return
|
370
|
+
:parent_id: 1
|
371
|
+
:return_value:
|
372
|
+
:class: String
|
373
|
+
:value: defined by including a module
|
374
|
+
YAML
|
375
|
+
|
376
|
+
load 'spec/fixtures/hook/singleton_method.rb'
|
377
|
+
setup = -> { SingletonMethod.new.do_include }
|
378
|
+
test_hook_behavior 'spec/fixtures/hook/singleton_method.rb', events_yaml, setup: setup do |s|
|
379
|
+
expect(s.added_method).to eq('defined by including a module')
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
it "doesn't hook a singleton method defined for an instance" do
|
384
|
+
# Ideally, Ruby would fire a TracePoint event when a singleton
|
385
|
+
# class gets created by defining a method on an instance. It
|
386
|
+
# currently doesn't, though, so there's no way for us to hook such
|
387
|
+
# a method.
|
388
|
+
#
|
389
|
+
# This example will fail if Ruby's behavior changes at some point
|
390
|
+
# in the future.
|
391
|
+
events_yaml = <<~YAML
|
392
|
+
--- []
|
393
|
+
YAML
|
394
|
+
|
395
|
+
load 'spec/fixtures/hook/singleton_method.rb'
|
396
|
+
setup = -> { SingletonMethod.new_with_instance_method }
|
397
|
+
test_hook_behavior 'spec/fixtures/hook/singleton_method.rb', events_yaml, setup: setup do |s|
|
398
|
+
expect(s.say_instance_defined).to eq('defined for an instance')
|
333
399
|
end
|
334
400
|
end
|
335
401
|
|
@@ -372,4 +438,165 @@ describe 'AppMap class Hooking' do
|
|
372
438
|
expect { ExceptionMethod.new.raise_exception }.to raise_exception
|
373
439
|
end
|
374
440
|
end
|
441
|
+
|
442
|
+
context 'ActiveSupport::SecurityUtils.secure_compare' do
|
443
|
+
it 'is hooked' do
|
444
|
+
events_yaml = <<~YAML
|
445
|
+
---
|
446
|
+
- :id: 1
|
447
|
+
:event: :call
|
448
|
+
:defined_class: Compare
|
449
|
+
:method_id: compare
|
450
|
+
:path: spec/fixtures/hook/compare.rb
|
451
|
+
:lineno: 4
|
452
|
+
:static: true
|
453
|
+
:parameters:
|
454
|
+
- :name: :s1
|
455
|
+
:class: String
|
456
|
+
:value: string
|
457
|
+
:kind: :req
|
458
|
+
- :name: :s2
|
459
|
+
:class: String
|
460
|
+
:value: string
|
461
|
+
:kind: :req
|
462
|
+
:receiver:
|
463
|
+
:class: Class
|
464
|
+
:value: Compare
|
465
|
+
- :id: 2
|
466
|
+
:event: :call
|
467
|
+
:defined_class: ActiveSupport::SecurityUtils
|
468
|
+
:method_id: secure_compare
|
469
|
+
:path: gems/activesupport-6.0.3.2/lib/active_support/security_utils.rb
|
470
|
+
:lineno: 26
|
471
|
+
:static: true
|
472
|
+
:parameters:
|
473
|
+
- :name: :a
|
474
|
+
:class: String
|
475
|
+
:value: string
|
476
|
+
:kind: :req
|
477
|
+
- :name: :b
|
478
|
+
:class: String
|
479
|
+
:value: string
|
480
|
+
:kind: :req
|
481
|
+
:receiver:
|
482
|
+
:class: Module
|
483
|
+
:value: ActiveSupport::SecurityUtils
|
484
|
+
- :id: 3
|
485
|
+
:event: :call
|
486
|
+
:defined_class: Digest::Instance
|
487
|
+
:method_id: digest
|
488
|
+
:path: Digest::Instance#digest
|
489
|
+
:static: false
|
490
|
+
:parameters:
|
491
|
+
- :name: arg
|
492
|
+
:class: String
|
493
|
+
:value: string
|
494
|
+
:kind: :rest
|
495
|
+
:receiver:
|
496
|
+
:class: Digest::SHA256
|
497
|
+
:value: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
498
|
+
- :id: 4
|
499
|
+
:event: :return
|
500
|
+
:parent_id: 3
|
501
|
+
:return_value:
|
502
|
+
:class: String
|
503
|
+
:value: "G2__)__qc____X____3_].\\x02y__.___/_"
|
504
|
+
- :id: 5
|
505
|
+
:event: :call
|
506
|
+
:defined_class: Digest::Instance
|
507
|
+
:method_id: digest
|
508
|
+
:path: Digest::Instance#digest
|
509
|
+
:static: false
|
510
|
+
:parameters:
|
511
|
+
- :name: arg
|
512
|
+
:class: String
|
513
|
+
:value: string
|
514
|
+
:kind: :rest
|
515
|
+
:receiver:
|
516
|
+
:class: Digest::SHA256
|
517
|
+
:value: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
518
|
+
- :id: 6
|
519
|
+
:event: :return
|
520
|
+
:parent_id: 5
|
521
|
+
:return_value:
|
522
|
+
:class: String
|
523
|
+
:value: "G2__)__qc____X____3_].\\x02y__.___/_"
|
524
|
+
- :id: 7
|
525
|
+
:event: :return
|
526
|
+
:parent_id: 2
|
527
|
+
:return_value:
|
528
|
+
:class: TrueClass
|
529
|
+
:value: 'true'
|
530
|
+
- :id: 8
|
531
|
+
:event: :return
|
532
|
+
:parent_id: 1
|
533
|
+
:return_value:
|
534
|
+
:class: TrueClass
|
535
|
+
:value: 'true'
|
536
|
+
YAML
|
537
|
+
|
538
|
+
test_hook_behavior 'spec/fixtures/hook/compare.rb', events_yaml do
|
539
|
+
expect(Compare.compare('string', 'string')).to be_truthy
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
it 'gets labeled in the classmap' do
|
544
|
+
classmap_yaml = <<~YAML
|
545
|
+
---
|
546
|
+
- :name: spec/fixtures/hook/compare.rb
|
547
|
+
:type: package
|
548
|
+
:children:
|
549
|
+
- :name: Compare
|
550
|
+
:type: class
|
551
|
+
:children:
|
552
|
+
- :name: compare
|
553
|
+
:type: function
|
554
|
+
:location: spec/fixtures/hook/compare.rb:4
|
555
|
+
:static: true
|
556
|
+
- :name: active_support
|
557
|
+
:type: package
|
558
|
+
:children:
|
559
|
+
- :name: ActiveSupport
|
560
|
+
:type: class
|
561
|
+
:children:
|
562
|
+
- :name: SecurityUtils
|
563
|
+
:type: class
|
564
|
+
:children:
|
565
|
+
- :name: secure_compare
|
566
|
+
:type: function
|
567
|
+
:location: gems/activesupport-6.0.3.2/lib/active_support/security_utils.rb:26
|
568
|
+
:static: true
|
569
|
+
:labels:
|
570
|
+
- security
|
571
|
+
- crypto
|
572
|
+
- :name: openssl
|
573
|
+
:type: package
|
574
|
+
:children:
|
575
|
+
- :name: Digest
|
576
|
+
:type: class
|
577
|
+
:children:
|
578
|
+
- :name: Instance
|
579
|
+
:type: class
|
580
|
+
:children:
|
581
|
+
- :name: digest
|
582
|
+
:type: function
|
583
|
+
:location: Digest::Instance#digest
|
584
|
+
:static: false
|
585
|
+
:labels:
|
586
|
+
- security
|
587
|
+
- crypto
|
588
|
+
YAML
|
589
|
+
|
590
|
+
config, tracer = invoke_test_file 'spec/fixtures/hook/compare.rb' do
|
591
|
+
expect(Compare.compare('string', 'string')).to be_truthy
|
592
|
+
end
|
593
|
+
cm = AppMap::ClassMap.build_from_methods(config, tracer.event_methods)
|
594
|
+
entry = cm[1][:children][0][:children][0][:children][0]
|
595
|
+
# Sanity check, make sure we got the right one
|
596
|
+
expect(entry[:name]).to eq('secure_compare')
|
597
|
+
spec = Gem::Specification.find_by_name('activesupport')
|
598
|
+
entry[:location].gsub!(spec.base_dir + '/', '')
|
599
|
+
expect(Diffy::Diff.new(classmap_yaml, cm.to_yaml).to_s).to eq('')
|
600
|
+
end
|
601
|
+
end
|
375
602
|
end
|
data/spec/open_spec.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe AppMap::Open do
|
6
|
+
context 'a block of Ruby code' do
|
7
|
+
it 'opens in the browser' do
|
8
|
+
appmap = AppMap.record do
|
9
|
+
File.read __FILE__
|
10
|
+
end
|
11
|
+
|
12
|
+
open = AppMap::Open.new(appmap)
|
13
|
+
server = open.run_server
|
14
|
+
page = Net::HTTP.get URI.parse("http://localhost:#{open.port}")
|
15
|
+
expect(page).to include(%(name="data" value='{"version))
|
16
|
+
server.kill
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|