appmap 0.28.1 → 0.34.1

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