tapping_device 0.5.1 → 0.5.6
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.
- checksums.yaml +4 -4
- data/.DS_Store +0 -0
- data/CHANGELOG.md +210 -0
- data/Gemfile.lock +15 -14
- data/README.md +192 -74
- data/images/print_mutations.png +0 -0
- data/lib/tapping_device.rb +112 -138
- data/lib/tapping_device/configurable.rb +27 -0
- data/lib/tapping_device/exceptions.rb +12 -0
- data/lib/tapping_device/method_hijacker.rb +51 -0
- data/lib/tapping_device/output.rb +42 -0
- data/lib/tapping_device/output/file_writer.rb +21 -0
- data/lib/tapping_device/output/payload.rb +179 -0
- data/lib/tapping_device/output/stdout_writer.rb +9 -0
- data/lib/tapping_device/output/writer.rb +20 -0
- data/lib/tapping_device/payload.rb +2 -3
- data/lib/tapping_device/trackable.rb +100 -19
- data/lib/tapping_device/trackers/association_call_tracker.rb +17 -0
- data/lib/tapping_device/trackers/initialization_tracker.rb +44 -0
- data/lib/tapping_device/trackers/method_call_tracker.rb +9 -0
- data/lib/tapping_device/trackers/mutation_tracker.rb +112 -0
- data/lib/tapping_device/trackers/passed_tracker.rb +16 -0
- data/lib/tapping_device/version.rb +1 -1
- data/tapping_device.gemspec +3 -1
- metadata +42 -15
- data/lib/tapping_device/output_payload.rb +0 -145
|
@@ -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
|
data/tapping_device.gemspec
CHANGED
|
@@ -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.
|
|
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-
|
|
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:
|
|
28
|
+
name: pry
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version:
|
|
34
|
-
type: :
|
|
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:
|
|
40
|
+
version: '0'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
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: :
|
|
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:
|
|
56
|
+
name: sqlite3
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
58
58
|
requirements:
|
|
59
|
-
- - "
|
|
59
|
+
- - ">="
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
|
-
version:
|
|
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:
|
|
68
|
+
version: 1.3.6
|
|
69
69
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
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/
|
|
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
|