appmap 0.36.0 → 0.37.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 102dab80f2d8cffd51fd1377583a1b063078cd77ab7ad4ffdcd20867a084b11b
4
- data.tar.gz: cbb87144593416f84099cf8a9d8ac18f18d9763dce28277320ae6a99ff59ec58
3
+ metadata.gz: fba24ecdb42c951741292378419eef430a56cc16816df46a14b2ae3e318f8f94
4
+ data.tar.gz: 67ff27467f72322c5a8514c90b0a6039e3fd1268dba103dea3b832eda95ac317
5
5
  SHA512:
6
- metadata.gz: a24e2ecb22a654d11e91ef28a7fdd961c178144f474a36e26c70e8e243bac18c13e3c5ff606f5ed2a058ddf6d98f9dbd0678a0f986ddfae4f18ed7524c27d41d
7
- data.tar.gz: 712cb7e18026aa80135af7ce54a7a8c412362355c0eb6b151a0f05354cf991ebe9402e298d9646b9fab2e5d673d3338a773a0b1b1d0e1846ec3ed970074a1f77
6
+ metadata.gz: 1823304c2733b46470481fd731a1f4650a80e769d96b47dbcdb0e5763e59930d6754068b4cf0729f9f748c55109575f3c8146b09030741fccfafd06cd45669b9
7
+ data.tar.gz: bbebe31041ed1720dc54b9522e4c6baac3b27d8d77be0622315657d0fa5b355c3e12474aa756811a6d747b563999c6e76206805c1392a015f92c6b863e8eeda0
data/.gitignore CHANGED
@@ -14,4 +14,5 @@ Gemfile.lock
14
14
  appmap.json
15
15
  .vscode
16
16
  .byebug_history
17
- /lib/appmap/appmap.bundle
17
+ /lib/appmap/appmap.bundle
18
+ *.so
@@ -1,3 +1,6 @@
1
+ AllCops:
2
+ NewCops: enable
3
+
1
4
  Layout/SpaceInsideArrayLiteralBrackets:
2
5
  Enabled: false
3
6
 
@@ -1,3 +1,6 @@
1
+ # v0.37.0
2
+ * Capture method source and comment.
3
+
1
4
  # v0.36.0
2
5
  * *appmap.yml* package definition may specify `gem`.
3
6
  * Skip loading the railtie if `APPMAP_INITIALIZE` environment variable
data/README.md CHANGED
@@ -1,3 +1,4 @@
1
+
1
2
  - [About](#about)
2
3
  - [Installation](#installation)
3
4
  - [Configuration](#configuration)
@@ -13,7 +14,7 @@
13
14
  - [Using fixture apps](#using-fixture-apps)
14
15
  - [`test/fixtures`](#testfixtures)
15
16
  - [`spec/fixtures`](#specfixtures)
16
- - [Build status](#build-status)
17
+
17
18
 
18
19
  # About
19
20
 
@@ -27,8 +28,9 @@ granular than a full debug trace. It's designed to be optimal for understanding
27
28
  There are several ways to record AppMaps of your Ruby program using the `appmap` gem:
28
29
 
29
30
  * Run your RSpec tests with the environment variable `APPMAP=true`. An AppMap will be generated for each spec.
30
- * Run your application server with AppMap remote recording enabled, and use the AppMap.
31
- browser extension to start, stop, and upload recordings.
31
+ * Run your application server with AppMap remote recording enabled, and use the [AppLand
32
+ browser extension](https://github.com/applandinc/appland-browser-extension) to start,
33
+ stop, and upload recordings.
32
34
  * Run the command `appmap record <program>` to record the entire execution of a program.
33
35
 
34
36
  Once you have recorded some AppMaps (for example, by running RSpec tests), you use the `appland upload` command
@@ -250,11 +252,11 @@ end
250
252
  $ bundle exec rails server
251
253
  ```
252
254
 
253
- 4. Open the AppApp browser extension and push `Start`.
255
+ 4. Open the AppLand browser extension and push `Start`.
254
256
 
255
257
  5. Use your app. For example, perform a login flow, or run through a manual UI test.
256
258
 
257
- 6. Open the AppApp browser extension and push `Stop`. The recording will be transferred to the AppLand website and opened in your browser.
259
+ 6. Open the AppLand browser extension and push `Stop`. The recording will be transferred to the AppLand website and opened in your browser.
258
260
 
259
261
  ## Ruby on Rails
260
262
 
@@ -267,6 +269,7 @@ Note that using this method is kind of a blunt instrument. Recording RSpecs and
267
269
  For instructions on uploading, see the documentation of the [AppLand CLI](https://github.com/applandinc/appland-cli).
268
270
 
269
271
  # Development
272
+ [![Build Status](https://travis-ci.org/applandinc/appmap-ruby.svg?branch=master)](https://travis-ci.org/applandinc/appmap-ruby)
270
273
 
271
274
  ## Running tests
272
275
 
@@ -291,7 +294,7 @@ $ bundle exec rake compile
291
294
  ### `test/fixtures`
292
295
 
293
296
  The fixture apps in `test/fixtures` are plain Ruby projects that exercise the basic functionality of the
294
- `appmap` gem. To develop in a fixture, simple enter the fixture directory and `bundle`.
297
+ `appmap` gem. To develop in a fixture, simply enter the fixture directory and `bundle`.
295
298
 
296
299
  ### `spec/fixtures`
297
300
 
@@ -341,5 +344,3 @@ Finished in 0.07357 seconds (files took 2.1 seconds to load)
341
344
  4 examples, 0 failures
342
345
  ```
343
346
 
344
- # Build status
345
- [![Build Status](https://travis-ci.org/applandinc/appmap-ruby.svg?branch=master)](https://travis-ci.org/applandinc/appmap-ruby)
data/Rakefile CHANGED
@@ -1,3 +1,4 @@
1
+ $: << File.join(__dir__, 'lib')
1
2
  require 'appmap/version'
2
3
  GEM_VERSION = AppMap::VERSION
3
4
 
@@ -27,6 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.add_dependency 'activesupport'
28
28
  spec.add_dependency 'faraday'
29
29
  spec.add_dependency 'gli'
30
+ spec.add_dependency 'method_source'
30
31
  spec.add_dependency 'parser'
31
32
  spec.add_dependency 'rack'
32
33
 
@@ -83,8 +83,8 @@ module AppMap
83
83
  end
84
84
 
85
85
  # Builds a class map from a config and a list of Ruby methods.
86
- def class_map(methods)
87
- ClassMap.build_from_methods(methods)
86
+ def class_map(methods, options = {})
87
+ ClassMap.build_from_methods(methods, options)
88
88
  end
89
89
 
90
90
  # Returns default metadata detected from the Ruby system and from the
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'method_source'
4
+
3
5
  module AppMap
4
6
  class ClassMap
5
7
  module HasChildren
@@ -48,7 +50,7 @@ module AppMap
48
50
  end
49
51
  end
50
52
  Function = Struct.new(:name) do
51
- attr_accessor :static, :location, :labels
53
+ attr_accessor :static, :location, :labels, :comment, :source
52
54
 
53
55
  def type
54
56
  'function'
@@ -60,24 +62,26 @@ module AppMap
60
62
  type: type,
61
63
  location: location,
62
64
  static: static,
63
- labels: labels
65
+ labels: labels,
66
+ comment: comment,
67
+ source: source
64
68
  }.delete_if { |_, v| v.nil? || v == [] }
65
69
  end
66
70
  end
67
71
  end
68
72
 
69
73
  class << self
70
- def build_from_methods(methods)
74
+ def build_from_methods(methods, options = {})
71
75
  root = Types::Root.new
72
76
  methods.each do |method|
73
- add_function root, method
77
+ add_function root, method, options
74
78
  end
75
79
  root.children.map(&:to_h)
76
80
  end
77
81
 
78
82
  protected
79
83
 
80
- def add_function(root, method)
84
+ def add_function(root, method, include_source: true)
81
85
  package = method.package
82
86
  static = method.static
83
87
 
@@ -109,6 +113,16 @@ module AppMap
109
113
  [ method.defined_class, static ? '.' : '#', method.name ].join
110
114
  end
111
115
 
116
+ if include_source
117
+ begin
118
+ function_info[:source] = method.source
119
+ comment = method.comment || ''
120
+ function_info[:comment] = comment unless comment.empty?
121
+ rescue MethodSource::SourceNotFoundError
122
+ # pass
123
+ end
124
+ end
125
+
112
126
  function_info[:labels] = package.labels if package.labels
113
127
  object_infos << function_info
114
128
 
@@ -148,7 +148,7 @@ module AppMap
148
148
 
149
149
  AppMap::RSpec.add_event_methods @trace.event_methods
150
150
 
151
- class_map = AppMap.class_map(@trace.event_methods)
151
+ class_map = AppMap.class_map(@trace.event_methods, include_source: false)
152
152
 
153
153
  description = []
154
154
  scope = ScopeExample.new(example)
@@ -3,7 +3,7 @@
3
3
  module AppMap
4
4
  URL = 'https://github.com/applandinc/appmap-ruby'
5
5
 
6
- VERSION = '0.36.0'
6
+ VERSION = '0.37.0'
7
7
 
8
- APPMAP_FORMAT_VERSION = '1.2'
8
+ APPMAP_FORMAT_VERSION = '1.3'
9
9
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe 'AppMap::ClassMap' do
6
+ describe '.build_from_methods' do
7
+ it 'includes source code if available' do
8
+ map = AppMap.class_map([scoped_method(method(:test_method))])
9
+ function = dig_map(map, 5)[0]
10
+ expect(function[:source]).to include 'test method body'
11
+ expect(function[:comment]).to include 'test method comment'
12
+ end
13
+
14
+ it 'can omit source code even if available' do
15
+ map = AppMap.class_map([scoped_method((method :test_method))], include_source: false)
16
+ function = dig_map(map, 5)[0]
17
+ expect(function).to_not include(:source)
18
+ expect(function).to_not include(:comment)
19
+ end
20
+
21
+ # test method comment
22
+ def test_method
23
+ 'test method body'
24
+ end
25
+
26
+ def scoped_method(method)
27
+ AppMap::Trace::ScopedMethod.new AppMap::Config::Package.new, method.receiver.class.name, method, false
28
+ end
29
+
30
+ def dig_map(map, depth)
31
+ return map if depth == 0
32
+
33
+ dig_map map[0][:children], depth - 1
34
+ end
35
+ end
36
+ end
@@ -112,6 +112,10 @@ describe 'AppMap class Hooking', docker: false do
112
112
  :type: function
113
113
  :location: spec/fixtures/hook/instance_method.rb:8
114
114
  :static: false
115
+ :source: |2
116
+ def say_default
117
+ 'default'
118
+ end
115
119
  YAML
116
120
  end
117
121
 
@@ -713,6 +717,10 @@ describe 'AppMap class Hooking', docker: false do
713
717
  :type: function
714
718
  :location: spec/fixtures/hook/compare.rb:4
715
719
  :static: true
720
+ :source: |2
721
+ def self.compare(s1, s2)
722
+ ActiveSupport::SecurityUtils.secure_compare(s1, s2)
723
+ end
716
724
  - :name: active_support
717
725
  :type: package
718
726
  :children:
@@ -729,6 +737,15 @@ describe 'AppMap class Hooking', docker: false do
729
737
  :labels:
730
738
  - security
731
739
  - crypto
740
+ :comment: |
741
+ # Constant time string comparison, for variable length strings.
742
+ #
743
+ # The values are first processed by SHA256, so that we don't leak length info
744
+ # via timing attacks.
745
+ :source: |2
746
+ def secure_compare(a, b)
747
+ fixed_length_secure_compare(::Digest::SHA256.digest(a), ::Digest::SHA256.digest(b)) && a == b
748
+ end
732
749
  - :name: openssl
733
750
  :type: package
734
751
  :children:
@@ -0,0 +1,55 @@
1
+ [
2
+ {
3
+ "name": "lib",
4
+ "type": "package",
5
+ "children": [
6
+ {
7
+ "name": "Example",
8
+ "type": "class",
9
+ "children": [
10
+ {
11
+ "name": "sign",
12
+ "type": "function",
13
+ "location": "lib/openssl_key_sign.rb:10",
14
+ "static": true,
15
+ "source": " def Example.sign\n key = OpenSSL::PKey::RSA.new 2048\n\n document = 'the document'\n\n digest = OpenSSL::Digest::SHA256.new\n key.sign digest, document\n end\n"
16
+ }
17
+ ]
18
+ }
19
+ ]
20
+ },
21
+ {
22
+ "name": "openssl",
23
+ "type": "package",
24
+ "children": [
25
+ {
26
+ "name": "OpenSSL",
27
+ "type": "class",
28
+ "children": [
29
+ {
30
+ "name": "PKey",
31
+ "type": "class",
32
+ "children": [
33
+ {
34
+ "name": "PKey",
35
+ "type": "class",
36
+ "children": [
37
+ {
38
+ "name": "sign",
39
+ "type": "function",
40
+ "location": "OpenSSL::PKey::PKey#sign",
41
+ "static": false,
42
+ "labels": [
43
+ "security",
44
+ "crypto"
45
+ ]
46
+ }
47
+ ]
48
+ }
49
+ ]
50
+ }
51
+ ]
52
+ }
53
+ ]
54
+ }
55
+ ]
@@ -0,0 +1,58 @@
1
+ [
2
+ {
3
+ "id": 1,
4
+ "event": "call",
5
+ "defined_class": "Example",
6
+ "method_id": "sign",
7
+ "path": "lib/openssl_key_sign.rb",
8
+ "lineno": 10,
9
+ "static": true,
10
+ "parameters": [
11
+
12
+ ],
13
+ "receiver": {
14
+ "class": "Module"
15
+ }
16
+ },
17
+ {
18
+ "id": 2,
19
+ "event": "call",
20
+ "defined_class": "OpenSSL::PKey::PKey",
21
+ "method_id": "sign",
22
+ "path": "OpenSSL::PKey::PKey#sign",
23
+ "static": false,
24
+ "parameters": [
25
+ {
26
+ "name": "arg",
27
+ "class": "OpenSSL::Digest::SHA256",
28
+ "value": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
29
+ "kind": "req"
30
+ },
31
+ {
32
+ "name": "arg",
33
+ "class": "String",
34
+ "value": "the document",
35
+ "kind": "req"
36
+ }
37
+ ],
38
+ "receiver": {
39
+ "class": "OpenSSL::PKey::RSA"
40
+ }
41
+ },
42
+ {
43
+ "id": 3,
44
+ "event": "return",
45
+ "parent_id": 2,
46
+ "return_value": {
47
+ "class": "String"
48
+ }
49
+ },
50
+ {
51
+ "id": 4,
52
+ "event": "return",
53
+ "parent_id": 1,
54
+ "return_value": {
55
+ "class": "String"
56
+ }
57
+ }
58
+ ]
@@ -18,6 +18,10 @@ class OpenSSLTest < Minitest::Test
18
18
  end
19
19
  end
20
20
 
21
+ def expectation(name)
22
+ File.read File.join __dir__, 'expectations', name
23
+ end
24
+
21
25
  def test_key_sign
22
26
  perform_test 'key_sign' do
23
27
  appmap_file = 'appmap.json'
@@ -26,62 +30,7 @@ class OpenSSLTest < Minitest::Test
26
30
  appmap = JSON.parse(File.read(appmap_file))
27
31
  assert_equal AppMap::APPMAP_FORMAT_VERSION, appmap['version']
28
32
  assert_equal [ { 'recorder' => 'lib/openssl_key_sign.rb' } ], appmap['metadata']
29
- assert_equal JSON.parse(<<~JSON), appmap['classMap']
30
- [
31
- {
32
- "name": "lib",
33
- "type": "package",
34
- "children": [
35
- {
36
- "name": "Example",
37
- "type": "class",
38
- "children": [
39
- {
40
- "name": "sign",
41
- "type": "function",
42
- "location": "lib/openssl_key_sign.rb:10",
43
- "static": true
44
- }
45
- ]
46
- }
47
- ]
48
- },
49
- {
50
- "name": "openssl",
51
- "type": "package",
52
- "children": [
53
- {
54
- "name": "OpenSSL",
55
- "type": "class",
56
- "children": [
57
- {
58
- "name": "PKey",
59
- "type": "class",
60
- "children": [
61
- {
62
- "name": "PKey",
63
- "type": "class",
64
- "children": [
65
- {
66
- "name": "sign",
67
- "type": "function",
68
- "location": "OpenSSL::PKey::PKey#sign",
69
- "static": false,
70
- "labels": [
71
- "security",
72
- "crypto"
73
- ]
74
- }
75
- ]
76
- }
77
- ]
78
- }
79
- ]
80
- }
81
- ]
82
- }
83
- ]
84
- JSON
33
+ assert_equal JSON.parse(expectation('openssl_test_key_sign1.json')), appmap['classMap']
85
34
  sanitized_events = appmap['events'].map(&:deep_symbolize_keys).map(&AppMap::Util.method(:sanitize_event)).map do |event|
86
35
  delete_value = ->(obj) { (obj || {}).delete(:value) }
87
36
  delete_value.call(event[:receiver])
@@ -89,66 +38,10 @@ class OpenSSLTest < Minitest::Test
89
38
  event
90
39
  end
91
40
 
92
- diff = Diffy::Diff.new(<<~JSON.strip, JSON.pretty_generate(sanitized_events).strip)
93
- [
94
- {
95
- "id": 1,
96
- "event": "call",
97
- "defined_class": "Example",
98
- "method_id": "sign",
99
- "path": "lib/openssl_key_sign.rb",
100
- "lineno": 10,
101
- "static": true,
102
- "parameters": [
103
-
104
- ],
105
- "receiver": {
106
- "class": "Module"
107
- }
108
- },
109
- {
110
- "id": 2,
111
- "event": "call",
112
- "defined_class": "OpenSSL::PKey::PKey",
113
- "method_id": "sign",
114
- "path": "OpenSSL::PKey::PKey#sign",
115
- "static": false,
116
- "parameters": [
117
- {
118
- "name": "arg",
119
- "class": "OpenSSL::Digest::SHA256",
120
- "value": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
121
- "kind": "req"
122
- },
123
- {
124
- "name": "arg",
125
- "class": "String",
126
- "value": "the document",
127
- "kind": "req"
128
- }
129
- ],
130
- "receiver": {
131
- "class": "OpenSSL::PKey::RSA"
132
- }
133
- },
134
- {
135
- "id": 3,
136
- "event": "return",
137
- "parent_id": 2,
138
- "return_value": {
139
- "class": "String"
140
- }
141
- },
142
- {
143
- "id": 4,
144
- "event": "return",
145
- "parent_id": 1,
146
- "return_value": {
147
- "class": "String"
148
- }
149
- }
150
- ]
151
- JSON
41
+ diff = Diffy::Diff.new(
42
+ expectation('openssl_test_key_sign2.json').strip,
43
+ JSON.pretty_generate(sanitized_events).strip
44
+ )
152
45
  assert_equal '', diff.to_s
153
46
  end
154
47
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appmap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.36.0
4
+ version: 0.37.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Gilpin
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-10-05 00:00:00.000000000 Z
11
+ date: 2020-11-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: method_source
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: parser
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -366,6 +380,7 @@ files:
366
380
  - package.json
367
381
  - spec/abstract_controller4_base_spec.rb
368
382
  - spec/abstract_controller_base_spec.rb
383
+ - spec/class_map_spec.rb
369
384
  - spec/config_spec.rb
370
385
  - spec/fixtures/hook/attr_accessor.rb
371
386
  - spec/fixtures/hook/compare.rb
@@ -528,6 +543,8 @@ files:
528
543
  - spec/util_spec.rb
529
544
  - test/cli_test.rb
530
545
  - test/cucumber_test.rb
546
+ - test/expectations/openssl_test_key_sign1.json
547
+ - test/expectations/openssl_test_key_sign2.json
531
548
  - test/fixtures/cli_record_test/appmap.yml
532
549
  - test/fixtures/cli_record_test/lib/cli_record_test/main.rb
533
550
  - test/fixtures/cucumber4_recorder/Gemfile