tapping_device 0.5.1 → 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ class TappingDevice
2
+ module Trackers
3
+ class MethodCallTracker < TappingDevice
4
+ def filter_condition_satisfied?(tp)
5
+ is_from_target?(tp)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,112 @@
1
+ class TappingDevice
2
+ module Trackers
3
+ class MutationTracker < TappingDevice
4
+ def initialize(options, &block)
5
+ options[:hijack_attr_methods] = true
6
+ super
7
+ @snapshot_stack = []
8
+ end
9
+
10
+ def track(object)
11
+ super
12
+ insert_snapshot_taking_trace_point
13
+ self
14
+ end
15
+
16
+ def stop!
17
+ super
18
+ @ivar_snapshot_trace_point.disable
19
+ end
20
+
21
+ private
22
+
23
+ # we need to snapshot instance variables at the beginning of every method call
24
+ # so we can get a correct state for the later comparison
25
+ def insert_snapshot_taking_trace_point
26
+ @ivar_snapshot_trace_point = build_minimum_trace_point(event_type: :call) do
27
+ snapshot_instance_variables
28
+ end
29
+
30
+ @ivar_snapshot_trace_point.enable unless TappingDevice.suspend_new
31
+ end
32
+
33
+ def filter_condition_satisfied?(tp)
34
+ return false unless is_from_target?(tp)
35
+
36
+ if snapshot_capturing_event?(tp)
37
+ true
38
+ else
39
+ @latest_instance_variables = target_instance_variables
40
+ @instance_variables_snapshot = @snapshot_stack.pop
41
+
42
+ @latest_instance_variables != @instance_variables_snapshot
43
+ end
44
+ end
45
+
46
+ def build_payload(tp:, filepath:, line_number:)
47
+ payload = super
48
+
49
+ if change_capturing_event?(tp)
50
+ payload[:ivar_changes] = capture_ivar_changes
51
+ end
52
+
53
+ payload
54
+ end
55
+
56
+ def capture_ivar_changes
57
+ changes = {}
58
+
59
+ additional_keys = @latest_instance_variables.keys - @instance_variables_snapshot.keys
60
+ additional_keys.each do |key|
61
+ changes[key] = {before: Output::Payload::UNDEFINED, after: @latest_instance_variables[key]}
62
+ end
63
+
64
+ removed_keys = @instance_variables_snapshot.keys - @latest_instance_variables.keys
65
+ removed_keys.each do |key|
66
+ changes[key] = {before: @instance_variables_snapshot[key], after: Output::Payload::UNDEFINED}
67
+ end
68
+
69
+ remained_keys = @latest_instance_variables.keys - additional_keys
70
+ remained_keys.each do |key|
71
+ next if @latest_instance_variables[key] == @instance_variables_snapshot[key]
72
+ changes[key] = {before: @instance_variables_snapshot[key], after: @latest_instance_variables[key]}
73
+ end
74
+
75
+ changes
76
+ end
77
+
78
+ def snapshot_instance_variables
79
+ @snapshot_stack.push(target_instance_variables)
80
+ end
81
+
82
+ def target_instance_variables
83
+ target.instance_variables.each_with_object({}) do |ivar, hash|
84
+ hash[ivar] = target.instance_variable_get(ivar)
85
+ end
86
+ end
87
+
88
+ def snapshot_capturing_event?(tp)
89
+ tp.event == :call
90
+ end
91
+
92
+ def change_capturing_event?(tp)
93
+ !snapshot_capturing_event?(tp)
94
+ end
95
+
96
+ # belows are debugging helpers
97
+ # I'll leave them for a while in case there's a bug in the tracker
98
+ def print_snapshot_stack(tp)
99
+ puts("===== STACK - #{tp.callee_id} (#{tp.event}) =====")
100
+ puts(@snapshot_stack)
101
+ puts("================ END STACK =================")
102
+ end
103
+
104
+ def print_state_comparison
105
+ puts("###############")
106
+ puts(@latest_instance_variables)
107
+ puts(@instance_variables_snapshot)
108
+ puts("###############")
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,16 @@
1
+ class TappingDevice
2
+ module Trackers
3
+ # PassedTracker tracks calls that use the target object as an argument
4
+ class PassedTracker < TappingDevice
5
+ def filter_condition_satisfied?(tp)
6
+ collect_arguments(tp).values.any? do |value|
7
+ # during comparison, Ruby might perform data type conversion like calling `to_sym` on the value
8
+ # but not every value supports every conversion methods
9
+ target == value rescue false
10
+ end
11
+ rescue
12
+ false
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,3 @@
1
1
  class TappingDevice
2
- VERSION = "0.5.1"
2
+ VERSION = "0.5.6"
3
3
  end
@@ -32,10 +32,12 @@ Gem::Specification.new do |spec|
32
32
  spec.add_dependency "activerecord", ">= 5.2"
33
33
  end
34
34
 
35
+ spec.add_dependency "pry" # for using Method#source in MutationTracker
36
+ spec.add_dependency "activesupport"
37
+
35
38
  spec.add_development_dependency "sqlite3", ">= 1.3.6"
36
39
  spec.add_development_dependency "database_cleaner"
37
40
  spec.add_development_dependency "bundler", "~> 2.0"
38
- spec.add_development_dependency "pry"
39
41
  spec.add_development_dependency "rake", "~> 13.0"
40
42
  spec.add_development_dependency "rspec", "~> 3.0"
41
43
  spec.add_development_dependency "simplecov", "0.17.1"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tapping_device
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - st0012
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-06-07 00:00:00.000000000 Z
11
+ date: 2020-07-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -25,27 +25,27 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '5.2'
27
27
  - !ruby/object:Gem::Dependency
28
- name: sqlite3
28
+ name: pry
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 1.3.6
34
- type: :development
33
+ version: '0'
34
+ type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 1.3.6
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: database_cleaner
42
+ name: activesupport
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
- type: :development
48
+ type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
@@ -53,21 +53,21 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: bundler
56
+ name: sqlite3
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '2.0'
61
+ version: 1.3.6
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '2.0'
68
+ version: 1.3.6
69
69
  - !ruby/object:Gem::Dependency
70
- name: pry
70
+ name: database_cleaner
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: rake
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -137,6 +151,7 @@ files:
137
151
  - ".rspec"
138
152
  - ".ruby-version"
139
153
  - ".travis.yml"
154
+ - CHANGELOG.md
140
155
  - CODE_OF_CONDUCT.md
141
156
  - Gemfile
142
157
  - Gemfile.lock
@@ -147,13 +162,25 @@ files:
147
162
  - bin/setup
148
163
  - images/print_calls - single entry.png
149
164
  - images/print_calls.png
165
+ - images/print_mutations.png
150
166
  - images/print_traces.png
151
167
  - lib/tapping_device.rb
168
+ - lib/tapping_device/configurable.rb
152
169
  - lib/tapping_device/exceptions.rb
153
170
  - lib/tapping_device/manageable.rb
154
- - lib/tapping_device/output_payload.rb
171
+ - lib/tapping_device/method_hijacker.rb
172
+ - lib/tapping_device/output.rb
173
+ - lib/tapping_device/output/file_writer.rb
174
+ - lib/tapping_device/output/payload.rb
175
+ - lib/tapping_device/output/stdout_writer.rb
176
+ - lib/tapping_device/output/writer.rb
155
177
  - lib/tapping_device/payload.rb
156
178
  - lib/tapping_device/trackable.rb
179
+ - lib/tapping_device/trackers/association_call_tracker.rb
180
+ - lib/tapping_device/trackers/initialization_tracker.rb
181
+ - lib/tapping_device/trackers/method_call_tracker.rb
182
+ - lib/tapping_device/trackers/mutation_tracker.rb
183
+ - lib/tapping_device/trackers/passed_tracker.rb
157
184
  - lib/tapping_device/version.rb
158
185
  - tapping_device.gemspec
159
186
  homepage: https://github.com/st0012/tapping_device
@@ -1,145 +0,0 @@
1
- class TappingDevice
2
- class OutputPayload < Payload
3
- alias :raw_arguments :arguments
4
- alias :raw_return_value :return_value
5
-
6
- def method_name(options = {})
7
- ":#{super(options)}"
8
- end
9
-
10
- def arguments(options = {})
11
- generate_string_result(raw_arguments, options[:inspect])
12
- end
13
-
14
- def return_value(options = {})
15
- generate_string_result(raw_return_value, options[:inspect])
16
- end
17
-
18
- def self.full_color_code(code)
19
- end
20
-
21
- COLOR_CODES = {
22
- green: 10,
23
- yellow: 11,
24
- blue: 12,
25
- megenta: 13,
26
- cyan: 14,
27
- orange: 214
28
- }
29
-
30
- COLORS = COLOR_CODES.each_with_object({}) do |(name, code), hash|
31
- hash[name] = "\u001b[38;5;#{code}m"
32
- end.merge(
33
- reset: "\u001b[0m",
34
- nocolor: ""
35
- )
36
-
37
- PAYLOAD_ATTRIBUTES = {
38
- method_name: {symbol: "", color: COLORS[:blue]},
39
- location: {symbol: "from:", color: COLORS[:green]},
40
- sql: {symbol: "QUERIES", color: COLORS[:nocolor]},
41
- return_value: {symbol: "=>", color: COLORS[:megenta]},
42
- arguments: {symbol: "<=", color: COLORS[:orange]},
43
- defined_class: {symbol: "#", color: COLORS[:yellow]}
44
- }
45
-
46
- PAYLOAD_ATTRIBUTES.each do |attribute, attribute_options|
47
- color = attribute_options[:color]
48
-
49
- alias_method "original_#{attribute}".to_sym, attribute
50
-
51
- # regenerate attributes with `colorize: true` support
52
- define_method attribute do |options = {}|
53
- call_result = send("original_#{attribute}", options)
54
-
55
- if options[:colorize]
56
- "#{color}#{call_result}#{COLORS[:reset]}"
57
- else
58
- call_result
59
- end
60
- end
61
-
62
- define_method "#{attribute}_with_color" do |options = {}|
63
- send(attribute, options.merge(colorize: true))
64
- end
65
-
66
- PAYLOAD_ATTRIBUTES.each do |and_attribute, and_attribute_options|
67
- next if and_attribute == attribute
68
-
69
- define_method "#{attribute}_and_#{and_attribute}" do |options = {}|
70
- "#{send(attribute, options)} #{and_attribute_options[:symbol]} #{send(and_attribute, options)}"
71
- end
72
-
73
- define_method "#{attribute}_and_#{and_attribute}_with_color" do |options = {}|
74
- send("#{attribute}_and_#{and_attribute}", options.merge(colorize: true))
75
- end
76
- end
77
- end
78
-
79
- def passed_at(options = {})
80
- with_method_head = options.fetch(:with_method_head, false)
81
- arg_name = raw_arguments.keys.detect { |k| raw_arguments[k] == target }
82
-
83
- return unless arg_name
84
-
85
- arg_name = ":#{arg_name}"
86
- arg_name = value_with_color(arg_name, :orange) if options[:colorize]
87
- msg = "Passed as #{arg_name} in '#{defined_class(options)}##{method_name(options)}' at #{location(options)}"
88
- msg += "\n > #{method_head.strip}" if with_method_head
89
- msg
90
- end
91
-
92
- def detail_call_info(options = {})
93
- <<~MSG
94
- #{method_name_and_defined_class(options)}
95
- from: #{location(options)}
96
- <= #{arguments(options)}
97
- => #{return_value(options)}
98
-
99
- MSG
100
- end
101
-
102
- private
103
-
104
- def value_with_color(value, color)
105
- "#{COLORS[color]}#{value}#{COLORS[:reset]}"
106
- end
107
-
108
- def generate_string_result(obj, inspect)
109
- case obj
110
- when Array
111
- array_to_string(obj, inspect)
112
- when Hash
113
- hash_to_string(obj, inspect)
114
- when String
115
- "\"#{obj}\""
116
- else
117
- inspect ? obj.inspect : obj.to_s
118
- end
119
- end
120
-
121
- def array_to_string(array, inspect)
122
- elements_string = array.map do |elem|
123
- generate_string_result(elem, inspect)
124
- end.join(", ")
125
- "[#{elements_string}]"
126
- end
127
-
128
- def hash_to_string(hash, inspect)
129
- elements_string = hash.map do |key, value|
130
- "#{key.to_s}: #{generate_string_result(value, inspect)}"
131
- end.join(", ")
132
- "{#{elements_string}}"
133
- end
134
-
135
- def obj_to_string(element, inspect)
136
- to_string_method = inspect ? :inspect : :to_s
137
-
138
- if !inspect && element.is_a?(String)
139
- "\"#{element}\""
140
- else
141
- element.send(to_string_method)
142
- end
143
- end
144
- end
145
- end