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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -1
  3. data/README.md +54 -2
  4. data/Rakefile +1 -1
  5. data/appmap.gemspec +2 -0
  6. data/lib/appmap.rb +25 -14
  7. data/lib/appmap/class_map.rb +25 -27
  8. data/lib/appmap/config.rb +115 -0
  9. data/lib/appmap/cucumber.rb +19 -2
  10. data/lib/appmap/event.rb +25 -16
  11. data/lib/appmap/hook.rb +89 -139
  12. data/lib/appmap/hook/method.rb +83 -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 +16 -8
  21. data/lib/appmap/util.rb +19 -0
  22. data/lib/appmap/version.rb +1 -1
  23. data/spec/abstract_controller4_base_spec.rb +1 -1
  24. data/spec/abstract_controller_base_spec.rb +9 -2
  25. data/spec/config_spec.rb +3 -3
  26. data/spec/fixtures/hook/compare.rb +7 -0
  27. data/spec/fixtures/hook/instance_method.rb +4 -0
  28. data/spec/hook_spec.rb +222 -37
  29. data/spec/open_spec.rb +19 -0
  30. data/spec/record_sql_rails_pg_spec.rb +56 -33
  31. data/spec/util_spec.rb +1 -1
  32. data/test/cli_test.rb +12 -2
  33. data/test/fixtures/minitest_recorder/Gemfile +5 -0
  34. data/test/fixtures/minitest_recorder/appmap.yml +3 -0
  35. data/test/fixtures/minitest_recorder/lib/hello.rb +5 -0
  36. data/test/fixtures/minitest_recorder/test/hello_test.rb +12 -0
  37. data/test/fixtures/openssl_recorder/Gemfile +3 -0
  38. data/test/fixtures/openssl_recorder/appmap.yml +3 -0
  39. data/test/fixtures/openssl_recorder/lib/openssl_cert_sign.rb +94 -0
  40. data/test/fixtures/openssl_recorder/lib/openssl_encrypt.rb +34 -0
  41. data/test/fixtures/openssl_recorder/lib/openssl_key_sign.rb +28 -0
  42. data/test/fixtures/process_recorder/appmap.yml +3 -0
  43. data/test/fixtures/process_recorder/hello.rb +9 -0
  44. data/test/minitest_test.rb +38 -0
  45. data/test/openssl_test.rb +203 -0
  46. data/test/record_process_test.rb +35 -0
  47. data/test/test_helper.rb +1 -0
  48. metadata +51 -2
@@ -47,17 +47,17 @@ describe 'AbstractControllerBase' do
47
47
  SERVER_REQUEST
48
48
  end
49
49
 
50
- it 'Properly captures method parameters in the appmap' do
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
- thread_id: .*
61
61
  static: false
62
62
  parameters:
63
63
  - name: params
@@ -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
@@ -2,9 +2,9 @@
2
2
 
3
3
  require 'rails_spec_helper'
4
4
  require 'active_support/core_ext'
5
- require 'appmap/hook'
5
+ require 'appmap/config'
6
6
 
7
- describe AppMap::Hook::Config do
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::Hook::Config.load(config_data)
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,7 @@
1
+ require 'active_support/security_utils'
2
+
3
+ class Compare
4
+ def self.compare(s1, s2)
5
+ ActiveSupport::SecurityUtils.secure_compare(s1, s2)
6
+ end
7
+ end
@@ -20,4 +20,8 @@ class InstanceMethod
20
20
  def say_block(&block)
21
21
  yield
22
22
  end
23
+
24
+ def say_the_time
25
+ Time.now.to_s
26
+ end
23
27
  end
@@ -15,48 +15,35 @@ module ShowYamlNulls
15
15
  end
16
16
  Psych::Visitors::YAMLTree.prepend(ShowYamlNulls)
17
17
 
18
- describe 'AppMap class Hooking' do
18
+ describe 'AppMap class Hooking', docker: false do
19
+ require 'appmap/util'
19
20
  def collect_events(tracer)
20
21
  [].tap do |events|
21
22
  while tracer.event?
22
23
  events << tracer.next_event.to_h
23
24
  end
24
- end.map do |event|
25
- event.delete(:thread_id)
26
- event.delete(:elapsed)
27
- delete_object_id = ->(obj) { (obj || {}).delete(:object_id) }
28
- delete_object_id.call(event[:receiver])
29
- delete_object_id.call(event[:return_value])
30
- (event[:parameters] || []).each(&delete_object_id)
31
- (event[:exceptions] || []).each(&delete_object_id)
32
-
33
- if event[:event] == :return
34
- # These should be removed from the appmap spec
35
- %i[defined_class method_id path lineno static].each do |obsolete_field|
36
- event.delete(obsolete_field)
37
- end
38
- end
39
- event
40
- end.to_yaml
25
+ end.map(&AppMap::Util.method(:sanitize_event)).to_yaml
41
26
  end
42
27
 
43
28
  def invoke_test_file(file, setup: nil, &block)
44
29
  AppMap.configuration = nil
45
- package = AppMap::Hook::Package.new(file, [])
46
- config = AppMap::Hook::Config.new('hook_spec', [ package ])
30
+ package = AppMap::Config::Package.new(file)
31
+ config = AppMap::Config.new('hook_spec', [ package ])
47
32
  AppMap.configuration = config
48
- AppMap::Hook.hook(config)
49
-
50
- setup_result = setup.call if setup
51
-
52
- tracer = AppMap.tracing.trace
53
- AppMap::Event.reset_id_counter
54
- begin
55
- load file
56
- yield setup_result
57
- ensure
58
- 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
59
45
  end
46
+
60
47
  [ config, tracer ]
61
48
  end
62
49
 
@@ -64,7 +51,8 @@ describe 'AppMap class Hooking' do
64
51
  config, tracer = invoke_test_file(file, setup: setup, &block)
65
52
 
66
53
  events = collect_events(tracer)
67
- expect(Diffy::Diff.new(events, events_yaml).to_s).to eq('')
54
+
55
+ expect(Diffy::Diff.new(events_yaml, events).to_s).to eq('')
68
56
 
69
57
  [ config, tracer ]
70
58
  end
@@ -94,7 +82,7 @@ describe 'AppMap class Hooking' do
94
82
  :class: String
95
83
  :value: default
96
84
  YAML
97
- 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
98
86
  expect(InstanceMethod.new.say_default).to eq('default')
99
87
  end
100
88
  end
@@ -104,7 +92,7 @@ describe 'AppMap class Hooking' do
104
92
  InstanceMethod.new.say_default
105
93
  end
106
94
  expect(tracer.event_methods.to_a.map(&:defined_class)).to eq([ 'InstanceMethod' ])
107
- 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 ])
108
96
  end
109
97
 
110
98
  it 'builds a class map of invoked methods' do
@@ -112,7 +100,7 @@ describe 'AppMap class Hooking' do
112
100
  InstanceMethod.new.say_default
113
101
  end
114
102
  class_map = AppMap.class_map(tracer.event_methods).to_yaml
115
- expect(Diffy::Diff.new(class_map, <<~YAML).to_s).to eq('')
103
+ expect(Diffy::Diff.new(<<~YAML, class_map).to_s).to eq('')
116
104
  ---
117
105
  - :name: spec/fixtures/hook/instance_method.rb
118
106
  :type: package
@@ -403,14 +391,14 @@ describe 'AppMap class Hooking' do
403
391
  events_yaml = <<~YAML
404
392
  --- []
405
393
  YAML
406
-
394
+
407
395
  load 'spec/fixtures/hook/singleton_method.rb'
408
396
  setup = -> { SingletonMethod.new_with_instance_method }
409
397
  test_hook_behavior 'spec/fixtures/hook/singleton_method.rb', events_yaml, setup: setup do |s|
410
398
  expect(s.say_instance_defined).to eq('defined for an instance')
411
399
  end
412
400
  end
413
-
401
+
414
402
  it 'Reports exceptions' do
415
403
  events_yaml = <<~YAML
416
404
  ---
@@ -450,4 +438,201 @@ describe 'AppMap class Hooking' do
450
438
  expect { ExceptionMethod.new.raise_exception }.to raise_exception
451
439
  end
452
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
602
+
603
+ it "doesn't cause expectations on Time.now to fail" do
604
+ events_yaml = <<~YAML
605
+ ---
606
+ - :id: 1
607
+ :event: :call
608
+ :defined_class: InstanceMethod
609
+ :method_id: say_the_time
610
+ :path: spec/fixtures/hook/instance_method.rb
611
+ :lineno: 24
612
+ :static: false
613
+ :parameters: []
614
+ :receiver:
615
+ :class: InstanceMethod
616
+ :value: Instance Method fixture
617
+ - :id: 2
618
+ :event: :return
619
+ :parent_id: 1
620
+ :return_value:
621
+ :class: String
622
+ :value: '2020-01-01 00:00:00 +0000'
623
+ YAML
624
+ test_hook_behavior 'spec/fixtures/hook/instance_method.rb', events_yaml do
625
+ require 'timecop'
626
+ begin
627
+ tz = ENV['TZ']
628
+ ENV['TZ'] = 'UTC'
629
+ Timecop.freeze(Time.utc('2020-01-01')) do
630
+ expect(Time).to receive(:now).exactly(3).times.and_call_original
631
+ expect(InstanceMethod.new.say_the_time).to be
632
+ end
633
+ ensure
634
+ ENV['TZ'] = tz
635
+ end
636
+ end
637
+ end
453
638
  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
@@ -1,6 +1,6 @@
1
1
  require 'rails_spec_helper'
2
2
 
3
- describe 'Record SQL queries in a Rails app' do
3
+ describe 'SQL events' do
4
4
  before(:all) { @fixture_dir = 'spec/fixtures/rails_users_app' }
5
5
  include_context 'Rails app pg database'
6
6
 
@@ -14,54 +14,77 @@ describe 'Record SQL queries in a Rails app' do
14
14
  end
15
15
 
16
16
  let(:tmpdir) { "tmp/spec/record_sql_rails_pg_spec" }
17
- let(:appmap) { JSON.parse(File.read(appmap_json)).to_yaml }
18
17
 
19
- context 'while creating a new record' do
18
+ describe 'fields' do
20
19
  let(:test_line_number) { 8 }
21
20
  let(:appmap_json) { File.join(tmpdir, 'appmap/rspec/Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json') }
21
+ let(:orm_module) { 'sequel' }
22
+ let(:appmap) { JSON.parse(File.read(appmap_json)) }
23
+ describe 'on a call event' do
24
+ let(:event) do
25
+ appmap['events'].find do |event|
26
+ event['event'] == 'call' &&
27
+ event.keys.include?('sql_query')
28
+ end
29
+ end
30
+ it 'do not include function-only fields' do
31
+ expect(event.keys).to_not include('defined_class')
32
+ expect(event.keys).to_not include('method_id')
33
+ expect(event.keys).to_not include('path')
34
+ expect(event.keys).to_not include('lineno')
35
+ end
36
+ end
37
+ end
38
+
39
+ describe 'in a Rails app' do
40
+ let(:appmap) { JSON.parse(File.read(appmap_json)).to_yaml }
41
+ context 'while creating a new record' do
42
+ let(:test_line_number) { 8 }
43
+ let(:appmap_json) { File.join(tmpdir, 'appmap/rspec/Api_UsersController_POST_api_users_with_required_parameters_creates_a_user.appmap.json') }
22
44
 
23
- context 'using Sequel ORM' do
24
- let(:orm_module) { 'sequel' }
25
- it 'detects the sql INSERT' do
26
- expect(appmap).to include(<<-SQL_QUERY.strip)
45
+ context 'using Sequel ORM' do
46
+ let(:orm_module) { 'sequel' }
47
+ it 'detects the sql INSERT' do
48
+ expect(appmap).to include(<<-SQL_QUERY.strip)
27
49
  sql_query:
28
50
  sql: INSERT INTO "users" ("login") VALUES ('alice') RETURNING *
29
- SQL_QUERY
51
+ SQL_QUERY
52
+ end
30
53
  end
31
- end
32
- context 'using ActiveRecord ORM' do
33
- let(:orm_module) { 'activerecord' }
34
- it 'detects the sql INSERT' do
35
- expect(appmap).to include(<<-SQL_QUERY.strip)
54
+ context 'using ActiveRecord ORM' do
55
+ let(:orm_module) { 'activerecord' }
56
+ it 'detects the sql INSERT' do
57
+ expect(appmap).to include(<<-SQL_QUERY.strip)
36
58
  sql_query:
37
59
  sql: INSERT INTO "users" ("login") VALUES ($1) RETURNING "id"
38
- SQL_QUERY
60
+ SQL_QUERY
61
+ end
39
62
  end
40
63
  end
41
- end
42
-
43
- context 'while listing records' do
44
- let(:test_line_number) { 23 }
45
- let(:appmap_json) { File.join(tmpdir, 'appmap/rspec/Api_UsersController_GET_api_users_lists_the_users.appmap.json') }
46
-
47
- context 'using Sequel ORM' do
48
- let(:orm_module) { 'sequel' }
49
- it 'detects the sql SELECT' do
50
- expect(appmap).to include(<<-SQL_QUERY.strip)
64
+
65
+ context 'while listing records' do
66
+ let(:test_line_number) { 23 }
67
+ let(:appmap_json) { File.join(tmpdir, 'appmap/rspec/Api_UsersController_GET_api_users_lists_the_users.appmap.json') }
68
+
69
+ context 'using Sequel ORM' do
70
+ let(:orm_module) { 'sequel' }
71
+ it 'detects the sql SELECT' do
72
+ expect(appmap).to include(<<-SQL_QUERY.strip)
51
73
  sql_query:
52
74
  sql: SELECT * FROM "users"
53
- SQL_QUERY
54
-
55
- expect(appmap).to include('sql:')
75
+ SQL_QUERY
76
+
77
+ expect(appmap).to include('sql:')
78
+ end
56
79
  end
57
- end
58
- context 'using ActiveRecord ORM' do
59
- let(:orm_module) { 'activerecord' }
60
- it 'detects the sql SELECT' do
61
- expect(appmap).to include(<<-SQL_QUERY.strip)
80
+ context 'using ActiveRecord ORM' do
81
+ let(:orm_module) { 'activerecord' }
82
+ it 'detects the sql SELECT' do
83
+ expect(appmap).to include(<<-SQL_QUERY.strip)
62
84
  sql_query:
63
85
  sql: SELECT "users".* FROM "users"
64
- SQL_QUERY
86
+ SQL_QUERY
87
+ end
65
88
  end
66
89
  end
67
90
  end