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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -0
  3. data/README.md +54 -2
  4. data/Rakefile +1 -1
  5. data/appmap.gemspec +1 -0
  6. data/lib/appmap.rb +25 -14
  7. data/lib/appmap/algorithm/stats.rb +2 -1
  8. data/lib/appmap/class_map.rb +26 -28
  9. data/lib/appmap/config.rb +115 -0
  10. data/lib/appmap/event.rb +28 -19
  11. data/lib/appmap/hook.rb +88 -129
  12. data/lib/appmap/hook/method.rb +78 -0
  13. data/lib/appmap/metadata.rb +1 -1
  14. data/lib/appmap/minitest.rb +141 -0
  15. data/lib/appmap/open.rb +57 -0
  16. data/lib/appmap/rails/action_handler.rb +7 -7
  17. data/lib/appmap/rails/sql_handler.rb +10 -8
  18. data/lib/appmap/record.rb +27 -0
  19. data/lib/appmap/rspec.rb +2 -2
  20. data/lib/appmap/trace.rb +17 -9
  21. data/lib/appmap/util.rb +19 -0
  22. data/lib/appmap/version.rb +1 -1
  23. data/package-lock.json +3 -3
  24. data/spec/abstract_controller4_base_spec.rb +1 -1
  25. data/spec/abstract_controller_base_spec.rb +9 -2
  26. data/spec/config_spec.rb +3 -3
  27. data/spec/fixtures/hook/compare.rb +7 -0
  28. data/spec/fixtures/hook/singleton_method.rb +54 -0
  29. data/spec/hook_spec.rb +280 -53
  30. data/spec/open_spec.rb +19 -0
  31. data/spec/record_sql_rails_pg_spec.rb +56 -33
  32. data/spec/util_spec.rb +1 -1
  33. data/test/cli_test.rb +14 -4
  34. data/test/fixtures/minitest_recorder/Gemfile +5 -0
  35. data/test/fixtures/minitest_recorder/appmap.yml +3 -0
  36. data/test/fixtures/minitest_recorder/lib/hello.rb +5 -0
  37. data/test/fixtures/minitest_recorder/test/hello_test.rb +12 -0
  38. data/test/fixtures/openssl_recorder/Gemfile +3 -0
  39. data/test/fixtures/openssl_recorder/appmap.yml +3 -0
  40. data/test/fixtures/openssl_recorder/lib/openssl_cert_sign.rb +94 -0
  41. data/test/fixtures/openssl_recorder/lib/openssl_encrypt.rb +34 -0
  42. data/test/fixtures/openssl_recorder/lib/openssl_key_sign.rb +28 -0
  43. data/test/fixtures/process_recorder/appmap.yml +3 -0
  44. data/test/fixtures/process_recorder/hello.rb +9 -0
  45. data/test/minitest_test.rb +38 -0
  46. data/test/openssl_test.rb +203 -0
  47. data/test/record_process_test.rb +35 -0
  48. data/test/test_helper.rb +1 -0
  49. metadata +38 -4
  50. data/spec/fixtures/hook/class_method.rb +0 -17
@@ -5,54 +5,54 @@ require 'appmap/hook'
5
5
  require 'appmap/event'
6
6
  require 'diffy'
7
7
 
8
- describe 'AppMap class Hooking' do
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 do |event|
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::Hook::Package.new(file, [])
36
- config = AppMap::Hook::Config.new('hook_spec', [ package ])
30
+ package = AppMap::Config::Package.new(file)
31
+ config = AppMap::Config.new('hook_spec', [ package ])
37
32
  AppMap.configuration = config
38
- AppMap::Hook.hook(config)
39
-
40
- tracer = AppMap.tracing.trace
41
- AppMap::Event.reset_id_counter
42
- begin
43
- load file
44
- yield
45
- ensure
46
- AppMap.tracing.delete(tracer)
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
- expect(Diffy::Diff.new(events, events_yaml).to_s).to eq('')
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
- config, tracer = test_hook_behavior 'spec/fixtures/hook/instance_method.rb', events_yaml do
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(&:method).map(&:to_s)).to eq([ InstanceMethod.public_instance_method(:say_default).to_s ])
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(class_map, <<~YAML).to_s).to eq('')
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: ClassMethod
263
+ :defined_class: SingletonMethod
264
264
  :method_id: say_default
265
- :path: spec/fixtures/hook/class_method.rb
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: ClassMethod
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/class_method.rb', events_yaml do
280
- expect(ClassMethod.say_default).to eq('default')
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: ClassMethod
289
+ :defined_class: SingletonMethod
290
290
  :method_id: say_class_defined
291
- :path: spec/fixtures/hook/class_method.rb
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: ClassMethod
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/class_method.rb', events_yaml do
306
- expect(ClassMethod.say_class_defined).to eq('defined with explicit class scope')
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: ClassMethod
315
+ :defined_class: SingletonMethod
316
316
  :method_id: say_self_defined
317
- :path: spec/fixtures/hook/class_method.rb
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: ClassMethod
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/class_method.rb', events_yaml do
332
- expect(ClassMethod.say_self_defined).to eq('defined with self class scope')
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
@@ -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='{&quot;version))
16
+ server.kill
17
+ end
18
+ end
19
+ end