appmap 0.28.0 → 0.34.0

Sign up to get free protection for your applications and to get access to all the features.
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