tapping_device 0.5.4 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +14 -4
- data/.gitignore +2 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +71 -0
- data/Gemfile +17 -0
- data/Makefile +3 -0
- data/lib/tapping_device.rb +12 -15
- data/lib/tapping_device/{configurable.rb → configuration.rb} +16 -9
- data/lib/tapping_device/method_hijacker.rb +4 -0
- data/lib/tapping_device/output.rb +6 -7
- data/lib/tapping_device/output/{payload.rb → payload_wrapper.rb} +47 -40
- data/lib/tapping_device/output/writer.rb +5 -3
- data/lib/tapping_device/payload.rb +23 -13
- data/lib/tapping_device/trackable.rb +1 -1
- data/lib/tapping_device/trackers/initialization_tracker.rb +22 -4
- data/lib/tapping_device/trackers/mutation_tracker.rb +3 -4
- data/lib/tapping_device/version.rb +1 -1
- data/tapping_device.gemspec +3 -18
- metadata +16 -116
- data/Gemfile.lock +0 -74
- data/lib/tapping_device/output/file_writer.rb +0 -21
- data/lib/tapping_device/output/stdout_writer.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9700574881872ce91eedf976fd5fa7c8506acfa1c0ea8819dc15eeef6e519a9b
|
4
|
+
data.tar.gz: 71b65b11ed57273a0c061f23141d5da92078a16f9a7c7f75e9aebb2de71c32c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7ead5f7a86fd942005a85394a0b59e309fc41bcf625369006ec848d2688722cf5fcc8651ad925c103536bef49e1d0a50f4aab75a177242c48873bc5703aea8cd
|
7
|
+
data.tar.gz: f7cab27f0e27937f8c5a8f8c08283ae376e5b7880dffa21857e48c3bcf6e17ed1cb265b5ef399b2f2d294bf5af97b6b26aef16589f97f502c9ebfa29178de0a8
|
data/.github/workflows/ruby.yml
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
name: Ruby
|
2
2
|
|
3
|
-
on:
|
3
|
+
on:
|
4
|
+
workflow_dispatch:
|
5
|
+
push:
|
6
|
+
branches:
|
7
|
+
- master
|
8
|
+
pull_request:
|
4
9
|
|
5
10
|
jobs:
|
6
11
|
test:
|
@@ -8,9 +13,12 @@ jobs:
|
|
8
13
|
runs-on: ${{ matrix.os }}
|
9
14
|
strategy:
|
10
15
|
matrix:
|
11
|
-
rails_version: ['5.2', '6']
|
12
|
-
ruby_version: ['2.
|
16
|
+
rails_version: ['5.2', '6.0.0', '6.1.0']
|
17
|
+
ruby_version: ['2.7', '3.0']
|
13
18
|
os: [ubuntu-latest]
|
19
|
+
exclude:
|
20
|
+
- ruby_version: '3.0'
|
21
|
+
rails_version: '5.2'
|
14
22
|
steps:
|
15
23
|
- uses: actions/checkout@v1
|
16
24
|
|
@@ -34,7 +42,9 @@ jobs:
|
|
34
42
|
bundle install --jobs 4 --retry 3
|
35
43
|
|
36
44
|
- name: Run test with Rails ${{ matrix.rails_version }}
|
37
|
-
|
45
|
+
env:
|
46
|
+
RAILS_VERSION: ${{ matrix.rails_version }}
|
47
|
+
run: make test
|
38
48
|
|
39
49
|
- name: Publish Test Coverage
|
40
50
|
uses: paambaati/codeclimate-action@v2.6.0
|
data/.gitignore
CHANGED
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
3.0.1
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,76 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v0.6.0](https://github.com/st0012/tapping_device/tree/v0.6.0) (2021-04-25)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.7...v0.6.0)
|
6
|
+
|
7
|
+
**Implemented enhancements:**
|
8
|
+
|
9
|
+
- Support Ruby 3.0 [\#71](https://github.com/st0012/tapping_device/pull/71) ([st0012](https://github.com/st0012))
|
10
|
+
|
11
|
+
**Merged pull requests:**
|
12
|
+
|
13
|
+
- Drop activerecord requirement [\#73](https://github.com/st0012/tapping_device/pull/73) ([st0012](https://github.com/st0012))
|
14
|
+
- Improve file-writing tests [\#72](https://github.com/st0012/tapping_device/pull/72) ([st0012](https://github.com/st0012))
|
15
|
+
- Simplify output logic with Ruby' Logger class [\#70](https://github.com/st0012/tapping_device/pull/70) ([st0012](https://github.com/st0012))
|
16
|
+
- Refactor Payload classes [\#68](https://github.com/st0012/tapping_device/pull/68) ([st0012](https://github.com/st0012))
|
17
|
+
|
18
|
+
## [v0.5.7](https://github.com/st0012/tapping_device/tree/v0.5.7) (2020-09-09)
|
19
|
+
|
20
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.6...v0.5.7)
|
21
|
+
|
22
|
+
**Implemented enhancements:**
|
23
|
+
|
24
|
+
- Use pastel in output payload [\#62](https://github.com/st0012/tapping_device/issues/62)
|
25
|
+
|
26
|
+
**Closed issues:**
|
27
|
+
|
28
|
+
- Support tag option [\#64](https://github.com/st0012/tapping_device/issues/64)
|
29
|
+
|
30
|
+
**Merged pull requests:**
|
31
|
+
|
32
|
+
- Use pastel to replace handmade colorizing logic [\#66](https://github.com/st0012/tapping_device/pull/66) ([st0012](https://github.com/st0012))
|
33
|
+
- Add tag option [\#65](https://github.com/st0012/tapping_device/pull/65) ([st0012](https://github.com/st0012))
|
34
|
+
|
35
|
+
## [v0.5.6](https://github.com/st0012/tapping_device/tree/v0.5.6) (2020-07-17)
|
36
|
+
|
37
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.5...v0.5.6)
|
38
|
+
|
39
|
+
## [v0.5.5](https://github.com/st0012/tapping_device/tree/v0.5.5) (2020-07-16)
|
40
|
+
|
41
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.4...v0.5.5)
|
42
|
+
|
43
|
+
**Fixed bugs:**
|
44
|
+
|
45
|
+
- InitializationTracker's logic can cause error [\#60](https://github.com/st0012/tapping_device/issues/60)
|
46
|
+
|
47
|
+
**Closed issues:**
|
48
|
+
|
49
|
+
- Refactor get\_method\_from\_object [\#59](https://github.com/st0012/tapping_device/issues/59)
|
50
|
+
|
51
|
+
**Merged pull requests:**
|
52
|
+
|
53
|
+
- Fix init tracker [\#61](https://github.com/st0012/tapping_device/pull/61) ([st0012](https://github.com/st0012))
|
54
|
+
|
55
|
+
## [v0.5.4](https://github.com/st0012/tapping_device/tree/v0.5.4) (2020-07-05)
|
56
|
+
|
57
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.3...v0.5.4)
|
58
|
+
|
59
|
+
**Closed issues:**
|
60
|
+
|
61
|
+
- Add with\_print\_calls method [\#52](https://github.com/st0012/tapping_device/issues/52)
|
62
|
+
- Tapping any instance of class [\#51](https://github.com/st0012/tapping_device/issues/51)
|
63
|
+
- Add ignore\_private option [\#50](https://github.com/st0012/tapping_device/issues/50)
|
64
|
+
|
65
|
+
**Merged pull requests:**
|
66
|
+
|
67
|
+
- Restructure README.md [\#58](https://github.com/st0012/tapping_device/pull/58) ([st0012](https://github.com/st0012))
|
68
|
+
- Better support on private methods [\#57](https://github.com/st0012/tapping_device/pull/57) ([st0012](https://github.com/st0012))
|
69
|
+
- Add with\_\* helpers \(e.g. with\_print\_calls\) [\#56](https://github.com/st0012/tapping_device/pull/56) ([st0012](https://github.com/st0012))
|
70
|
+
- Add force\_recording option for debugging [\#55](https://github.com/st0012/tapping_device/pull/55) ([st0012](https://github.com/st0012))
|
71
|
+
- Add print\_instance\_\* and write\_instance\_\* helpers [\#54](https://github.com/st0012/tapping_device/pull/54) ([st0012](https://github.com/st0012))
|
72
|
+
- Fix tap\_init by adding c\_\* event type [\#53](https://github.com/st0012/tapping_device/pull/53) ([st0012](https://github.com/st0012))
|
73
|
+
|
3
74
|
## [v0.5.3](https://github.com/st0012/tapping_device/tree/v0.5.3) (2020-06-21)
|
4
75
|
|
5
76
|
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.2...v0.5.3)
|
data/Gemfile
CHANGED
@@ -2,3 +2,20 @@ source "https://rubygems.org"
|
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in tapping_device.gemspec
|
4
4
|
gemspec
|
5
|
+
|
6
|
+
rails_version = ENV["RAILS_VERSION"]
|
7
|
+
rails_version = "6.1.0" if rails_version.nil?
|
8
|
+
|
9
|
+
if rails_version.to_f < 6
|
10
|
+
gem "sqlite3", "~> 1.3.0"
|
11
|
+
else
|
12
|
+
gem "sqlite3"
|
13
|
+
end
|
14
|
+
|
15
|
+
gem "activerecord", "~> #{rails_version}"
|
16
|
+
|
17
|
+
gem "rake", "~> 13.0"
|
18
|
+
gem "rspec", "~> 3.0"
|
19
|
+
gem "simplecov", "~> 0.17.1"
|
20
|
+
gem "database_cleaner", "~> 2.0.0"
|
21
|
+
gem "pry"
|
data/Makefile
ADDED
data/lib/tapping_device.rb
CHANGED
@@ -1,13 +1,11 @@
|
|
1
|
-
require "
|
2
|
-
require "active_support/core_ext/module/introspection"
|
3
|
-
require "pry" # for using Method#source
|
1
|
+
require "method_source" # for using Method#source
|
4
2
|
|
5
3
|
require "tapping_device/version"
|
6
4
|
require "tapping_device/manageable"
|
7
5
|
require "tapping_device/payload"
|
8
6
|
require "tapping_device/output"
|
9
7
|
require "tapping_device/trackable"
|
10
|
-
require "tapping_device/
|
8
|
+
require "tapping_device/configuration"
|
11
9
|
require "tapping_device/exceptions"
|
12
10
|
require "tapping_device/method_hijacker"
|
13
11
|
require "tapping_device/trackers/initialization_tracker"
|
@@ -28,7 +26,6 @@ class TappingDevice
|
|
28
26
|
|
29
27
|
extend Manageable
|
30
28
|
|
31
|
-
include Configurable
|
32
29
|
include Output::Helpers
|
33
30
|
|
34
31
|
def initialize(options = {}, &block)
|
@@ -120,8 +117,10 @@ class TappingDevice
|
|
120
117
|
|
121
118
|
# this needs to be placed upfront so we can exclude noise before doing more work
|
122
119
|
def should_be_skipped_by_paths?(filepath)
|
123
|
-
options[:exclude_by_paths]
|
124
|
-
|
120
|
+
exclude_by_paths = options[:exclude_by_paths]
|
121
|
+
filter_by_paths = options[:filter_by_paths]
|
122
|
+
exclude_by_paths.any? { |pattern| pattern.match?(filepath) } ||
|
123
|
+
(filter_by_paths && !filter_by_paths.empty? && !filter_by_paths.any? { |pattern| pattern.match?(filepath) })
|
125
124
|
end
|
126
125
|
|
127
126
|
def is_tapping_device_call?(tp)
|
@@ -137,11 +136,11 @@ class TappingDevice
|
|
137
136
|
end
|
138
137
|
|
139
138
|
def with_condition_satisfied?(payload)
|
140
|
-
@with_condition.
|
139
|
+
@with_condition.nil? || @with_condition.call(payload)
|
141
140
|
end
|
142
141
|
|
143
142
|
def build_payload(tp:, filepath:, line_number:)
|
144
|
-
Payload.
|
143
|
+
Payload.new(
|
145
144
|
target: @target,
|
146
145
|
receiver: tp.self,
|
147
146
|
method_name: tp.callee_id,
|
@@ -152,16 +151,14 @@ class TappingDevice
|
|
152
151
|
line_number: line_number,
|
153
152
|
defined_class: tp.defined_class,
|
154
153
|
trace: get_traces(tp),
|
155
|
-
is_private_call
|
154
|
+
is_private_call: tp.defined_class.private_method_defined?(tp.callee_id),
|
155
|
+
tag: options[:tag],
|
156
156
|
tp: tp
|
157
|
-
|
157
|
+
)
|
158
158
|
end
|
159
159
|
|
160
160
|
def get_method_object_from(target, method_name)
|
161
|
-
|
162
|
-
rescue ArgumentError
|
163
|
-
method_method = Object.method(:method).unbind
|
164
|
-
method_method.bind(target).call(method_name)
|
161
|
+
Object.instance_method(:method).bind(target).call(method_name)
|
165
162
|
rescue NameError
|
166
163
|
# if any part of the program uses Refinement to extend its methods
|
167
164
|
# we might still get NoMethodError when trying to get that method outside the scope
|
@@ -1,10 +1,5 @@
|
|
1
|
-
require "active_support/configurable"
|
2
|
-
require "active_support/concern"
|
3
|
-
|
4
1
|
class TappingDevice
|
5
|
-
|
6
|
-
extend ActiveSupport::Concern
|
7
|
-
|
2
|
+
class Configuration
|
8
3
|
DEFAULTS = {
|
9
4
|
filter_by_paths: [],
|
10
5
|
exclude_by_paths: [],
|
@@ -16,12 +11,24 @@ class TappingDevice
|
|
16
11
|
only_private: false
|
17
12
|
}.merge(TappingDevice::Output::DEFAULT_OPTIONS)
|
18
13
|
|
19
|
-
|
20
|
-
|
14
|
+
def initialize
|
15
|
+
@options = {}
|
21
16
|
|
22
17
|
DEFAULTS.each do |key, value|
|
23
|
-
|
18
|
+
@options[key] = value
|
24
19
|
end
|
25
20
|
end
|
21
|
+
|
22
|
+
def [](key)
|
23
|
+
@options[key]
|
24
|
+
end
|
25
|
+
|
26
|
+
def []=(key, value)
|
27
|
+
@options[key] = value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.config
|
32
|
+
@config ||= Configuration.new
|
26
33
|
end
|
27
34
|
end
|
@@ -20,10 +20,14 @@ class TappingDevice
|
|
20
20
|
|
21
21
|
def is_writer_method?(method_name)
|
22
22
|
has_definition_source?(method_name) && method_name.match?(/\w+=/) && target.method(method_name).source.match?(/attr_writer|attr_accessor/)
|
23
|
+
rescue MethodSource::SourceNotFoundError
|
24
|
+
false
|
23
25
|
end
|
24
26
|
|
25
27
|
def is_reader_method?(method_name)
|
26
28
|
has_definition_source?(method_name) && target.method(method_name).source.match?(/attr_reader|attr_accessor/)
|
29
|
+
rescue MethodSource::SourceNotFoundError
|
30
|
+
false
|
27
31
|
end
|
28
32
|
|
29
33
|
def has_definition_source?(method_name)
|
@@ -1,7 +1,6 @@
|
|
1
|
-
require "
|
1
|
+
require "logger"
|
2
|
+
require "tapping_device/output/payload_wrapper"
|
2
3
|
require "tapping_device/output/writer"
|
3
|
-
require "tapping_device/output/stdout_writer"
|
4
|
-
require "tapping_device/output/file_writer"
|
5
4
|
|
6
5
|
class TappingDevice
|
7
6
|
module Output
|
@@ -13,16 +12,16 @@ class TappingDevice
|
|
13
12
|
|
14
13
|
module Helpers
|
15
14
|
def and_write(payload_method = nil, options: {}, &block)
|
16
|
-
and_output(payload_method, options: options,
|
15
|
+
and_output(payload_method, options: options, logger: Logger.new(options[:log_file]), &block)
|
17
16
|
end
|
18
17
|
|
19
18
|
def and_print(payload_method = nil, options: {}, &block)
|
20
|
-
and_output(payload_method, options: options,
|
19
|
+
and_output(payload_method, options: options, logger: Logger.new($stdout), &block)
|
21
20
|
end
|
22
21
|
|
23
|
-
def and_output(payload_method = nil, options: {},
|
22
|
+
def and_output(payload_method = nil, options: {}, logger:, &block)
|
24
23
|
output_block = generate_output_block(payload_method, block)
|
25
|
-
@output_writer =
|
24
|
+
@output_writer = Writer.new(options: options, output_block: output_block, logger: logger)
|
26
25
|
self
|
27
26
|
end
|
28
27
|
|
@@ -1,17 +1,44 @@
|
|
1
|
+
require "pastel"
|
2
|
+
|
1
3
|
class TappingDevice
|
2
4
|
module Output
|
3
|
-
class
|
5
|
+
class PayloadWrapper
|
4
6
|
UNDEFINED = "[undefined]"
|
7
|
+
PRIVATE_MARK = " (private)"
|
8
|
+
|
9
|
+
PASTEL = Pastel.new
|
10
|
+
PASTEL.alias_color(:orange, :bright_red, :bright_yellow)
|
11
|
+
|
12
|
+
TappingDevice::Payload::ATTRS.each do |attr|
|
13
|
+
define_method attr do |options = {}|
|
14
|
+
@payload.send(attr)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
alias :is_private_call? :is_private_call
|
19
|
+
|
20
|
+
def method_head
|
21
|
+
@payload.method_head
|
22
|
+
end
|
23
|
+
|
24
|
+
def location(options = {})
|
25
|
+
@payload.location(options)
|
26
|
+
end
|
5
27
|
|
6
28
|
alias :raw_arguments :arguments
|
7
29
|
alias :raw_return_value :return_value
|
8
30
|
|
31
|
+
def initialize(payload)
|
32
|
+
@payload = payload
|
33
|
+
end
|
34
|
+
|
9
35
|
def method_name(options = {})
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
36
|
+
name = ":#{@payload.method_name}"
|
37
|
+
|
38
|
+
name += " [#{tag}]" if tag
|
39
|
+
name += PRIVATE_MARK if is_private_call?
|
40
|
+
|
41
|
+
name
|
15
42
|
end
|
16
43
|
|
17
44
|
def arguments(options = {})
|
@@ -22,29 +49,13 @@ class TappingDevice
|
|
22
49
|
generate_string_result(raw_return_value, options[:inspect])
|
23
50
|
end
|
24
51
|
|
25
|
-
COLOR_CODES = {
|
26
|
-
green: 10,
|
27
|
-
yellow: 11,
|
28
|
-
blue: 12,
|
29
|
-
megenta: 13,
|
30
|
-
cyan: 14,
|
31
|
-
orange: 214
|
32
|
-
}
|
33
|
-
|
34
|
-
COLORS = COLOR_CODES.each_with_object({}) do |(name, code), hash|
|
35
|
-
hash[name] = "\u001b[38;5;#{code}m"
|
36
|
-
end.merge(
|
37
|
-
reset: "\u001b[0m",
|
38
|
-
nocolor: ""
|
39
|
-
)
|
40
|
-
|
41
52
|
PAYLOAD_ATTRIBUTES = {
|
42
|
-
method_name: {symbol: "", color:
|
43
|
-
location: {symbol: "from:", color:
|
44
|
-
return_value: {symbol: "=>", color:
|
45
|
-
arguments: {symbol: "<=", color:
|
46
|
-
ivar_changes: {symbol: "changes:\n", color:
|
47
|
-
defined_class: {symbol: "#", color:
|
53
|
+
method_name: {symbol: "", color: :bright_blue},
|
54
|
+
location: {symbol: "from:", color: :green},
|
55
|
+
return_value: {symbol: "=>", color: :magenta},
|
56
|
+
arguments: {symbol: "<=", color: :orange},
|
57
|
+
ivar_changes: {symbol: "changes:\n", color: :blue},
|
58
|
+
defined_class: {symbol: "#", color: :yellow}
|
48
59
|
}
|
49
60
|
|
50
61
|
PAYLOAD_ATTRIBUTES.each do |attribute, attribute_options|
|
@@ -54,10 +65,10 @@ class TappingDevice
|
|
54
65
|
|
55
66
|
# regenerate attributes with `colorize: true` support
|
56
67
|
define_method attribute do |options = {}|
|
57
|
-
call_result = send("original_#{attribute}", options)
|
68
|
+
call_result = send("original_#{attribute}", options).to_s
|
58
69
|
|
59
70
|
if options[:colorize]
|
60
|
-
|
71
|
+
PASTEL.send(color, call_result)
|
61
72
|
else
|
62
73
|
call_result
|
63
74
|
end
|
@@ -87,7 +98,7 @@ class TappingDevice
|
|
87
98
|
return unless arg_name
|
88
99
|
|
89
100
|
arg_name = ":#{arg_name}"
|
90
|
-
arg_name =
|
101
|
+
arg_name = PASTEL.orange(arg_name) if options[:colorize]
|
91
102
|
msg = "Passed as #{arg_name} in '#{defined_class(options)}##{method_name(options)}' at #{location(options)}\n"
|
92
103
|
msg += " > #{method_head}\n" if with_method_head
|
93
104
|
msg
|
@@ -104,17 +115,17 @@ class TappingDevice
|
|
104
115
|
end
|
105
116
|
|
106
117
|
def ivar_changes(options = {})
|
107
|
-
|
118
|
+
@payload.ivar_changes.map do |ivar, value_changes|
|
108
119
|
before = generate_string_result(value_changes[:before], options[:inspect])
|
109
120
|
after = generate_string_result(value_changes[:after], options[:inspect])
|
110
121
|
|
111
122
|
if options[:colorize]
|
112
|
-
ivar =
|
113
|
-
before =
|
114
|
-
after =
|
123
|
+
ivar = PASTEL.orange(ivar.to_s)
|
124
|
+
before = PASTEL.bright_blue(before.to_s)
|
125
|
+
after = PASTEL.bright_blue(after.to_s)
|
115
126
|
end
|
116
127
|
|
117
|
-
" #{ivar}: #{before
|
128
|
+
" #{ivar}: #{before} => #{after}"
|
118
129
|
end.join("\n")
|
119
130
|
end
|
120
131
|
|
@@ -130,10 +141,6 @@ class TappingDevice
|
|
130
141
|
|
131
142
|
private
|
132
143
|
|
133
|
-
def value_with_color(value, color)
|
134
|
-
"#{COLORS[color]}#{value}#{COLORS[:reset]}"
|
135
|
-
end
|
136
|
-
|
137
144
|
def generate_string_result(obj, inspect)
|
138
145
|
case obj
|
139
146
|
when Array
|
@@ -1,19 +1,21 @@
|
|
1
1
|
class TappingDevice
|
2
2
|
module Output
|
3
3
|
class Writer
|
4
|
-
def initialize(options
|
4
|
+
def initialize(options:, output_block:, logger:)
|
5
5
|
@options = options
|
6
6
|
@output_block = output_block
|
7
|
+
@logger = logger
|
7
8
|
end
|
8
9
|
|
9
10
|
def write!(payload)
|
10
|
-
|
11
|
+
output = generate_output(payload)
|
12
|
+
@logger << output
|
11
13
|
end
|
12
14
|
|
13
15
|
private
|
14
16
|
|
15
17
|
def generate_output(payload)
|
16
|
-
@output_block.call(
|
18
|
+
@output_block.call(PayloadWrapper.new(payload), @options)
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
@@ -1,22 +1,32 @@
|
|
1
1
|
class TappingDevice
|
2
|
-
class Payload
|
2
|
+
class Payload
|
3
3
|
ATTRS = [
|
4
4
|
:target, :receiver, :method_name, :method_object, :arguments, :return_value, :filepath, :line_number,
|
5
|
-
:defined_class, :trace, :tp, :ivar_changes, :is_private_call
|
5
|
+
:defined_class, :trace, :tag, :tp, :ivar_changes, :is_private_call
|
6
6
|
]
|
7
7
|
|
8
|
-
ATTRS
|
9
|
-
|
10
|
-
|
11
|
-
end
|
12
|
-
end
|
8
|
+
attr_accessor(*ATTRS)
|
9
|
+
|
10
|
+
alias :is_private_call? :is_private_call
|
13
11
|
|
14
|
-
def
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
12
|
+
def initialize(
|
13
|
+
target:, receiver:, method_name:, method_object:, arguments:, return_value:, filepath:, line_number:,
|
14
|
+
defined_class:, trace:, tag:, tp:, is_private_call:
|
15
|
+
)
|
16
|
+
@target = target
|
17
|
+
@receiver = receiver
|
18
|
+
@method_name = method_name
|
19
|
+
@method_object = method_object
|
20
|
+
@arguments = arguments
|
21
|
+
@return_value = return_value
|
22
|
+
@filepath = filepath
|
23
|
+
@line_number = line_number
|
24
|
+
@defined_class = defined_class
|
25
|
+
@trace = trace
|
26
|
+
@tag = tag
|
27
|
+
@tp = tp
|
28
|
+
@ivar_changes = {}
|
29
|
+
@is_private_call = is_private_call
|
20
30
|
end
|
21
31
|
|
22
32
|
def method_head
|
@@ -36,7 +36,7 @@ class TappingDevice
|
|
36
36
|
define_method "#{output_action}_instance_#{subject}" do |target_klass, options = {}|
|
37
37
|
collection_proxy = AsyncCollectionProxy.new
|
38
38
|
|
39
|
-
tap_init!(target_klass, options) do |payload|
|
39
|
+
tap_init!(target_klass, options.merge(force_recording: true)) do |payload|
|
40
40
|
collection_proxy << send(helper_method_name, payload.return_value, options)
|
41
41
|
end
|
42
42
|
|
@@ -9,10 +9,19 @@ class TappingDevice
|
|
9
9
|
@options[:event_type] = [event_type, "c_#{event_type}"]
|
10
10
|
end
|
11
11
|
|
12
|
+
def track(object)
|
13
|
+
super
|
14
|
+
@is_active_record_model = defined?(ActiveRecord) && target.ancestors.include?(ActiveRecord::Base)
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
12
18
|
def build_payload(tp:, filepath:, line_number:)
|
13
19
|
payload = super
|
14
|
-
|
15
|
-
payload
|
20
|
+
|
21
|
+
return payload if @is_active_record_model
|
22
|
+
|
23
|
+
payload.return_value = payload.receiver
|
24
|
+
payload.receiver = target
|
16
25
|
payload
|
17
26
|
end
|
18
27
|
|
@@ -24,8 +33,17 @@ class TappingDevice
|
|
24
33
|
receiver = tp.self
|
25
34
|
method_name = tp.callee_id
|
26
35
|
|
27
|
-
if
|
28
|
-
|
36
|
+
if @is_active_record_model
|
37
|
+
# ActiveRecord redefines model classes' .new method,
|
38
|
+
# so instead of calling Model#initialize, it'll actually call Model.new
|
39
|
+
# see https://github.com/rails/rails/blob/master/activerecord/lib/active_record/inheritance.rb#L50
|
40
|
+
method_name == :new &&
|
41
|
+
receiver.is_a?(Class) &&
|
42
|
+
# this checks if the model class is the target class or a subclass of it
|
43
|
+
receiver.ancestors.include?(target) &&
|
44
|
+
# Model.new triggers both c_return and return events. so we should only return in 1 type of the events
|
45
|
+
# otherwise the callback will be triggered twice
|
46
|
+
tp.event == :return
|
29
47
|
else
|
30
48
|
method_name == :initialize && receiver.is_a?(target)
|
31
49
|
end
|
@@ -47,7 +47,7 @@ class TappingDevice
|
|
47
47
|
payload = super
|
48
48
|
|
49
49
|
if change_capturing_event?(tp)
|
50
|
-
payload
|
50
|
+
payload.ivar_changes = capture_ivar_changes
|
51
51
|
end
|
52
52
|
|
53
53
|
payload
|
@@ -55,15 +55,14 @@ class TappingDevice
|
|
55
55
|
|
56
56
|
def capture_ivar_changes
|
57
57
|
changes = {}
|
58
|
-
|
59
58
|
additional_keys = @latest_instance_variables.keys - @instance_variables_snapshot.keys
|
60
59
|
additional_keys.each do |key|
|
61
|
-
changes[key] = {before: Output::
|
60
|
+
changes[key] = {before: Output::PayloadWrapper::UNDEFINED, after: @latest_instance_variables[key]}
|
62
61
|
end
|
63
62
|
|
64
63
|
removed_keys = @instance_variables_snapshot.keys - @latest_instance_variables.keys
|
65
64
|
removed_keys.each do |key|
|
66
|
-
changes[key] = {before: @instance_variables_snapshot[key], after: Output::
|
65
|
+
changes[key] = {before: @instance_variables_snapshot[key], after: Output::PayloadWrapper::UNDEFINED}
|
67
66
|
end
|
68
67
|
|
69
68
|
remained_keys = @latest_instance_variables.keys - additional_keys
|
data/tapping_device.gemspec
CHANGED
@@ -15,30 +15,15 @@ Gem::Specification.new do |spec|
|
|
15
15
|
|
16
16
|
spec.metadata["homepage_uri"] = spec.homepage
|
17
17
|
spec.metadata["source_code_uri"] = "https://github.com/st0012/tapping_device"
|
18
|
-
spec.metadata["changelog_uri"] = "https://github.com/st0012/tapping_device/
|
18
|
+
spec.metadata["changelog_uri"] = "https://github.com/st0012/tapping_device/CHANGELOG.md"
|
19
19
|
|
20
20
|
# Specify which files should be added to the gem when it is released.
|
21
21
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
22
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
23
23
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
24
24
|
end
|
25
|
-
spec.bindir = "exe"
|
26
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
25
|
spec.require_paths = ["lib"]
|
28
26
|
|
29
|
-
|
30
|
-
|
31
|
-
else
|
32
|
-
spec.add_dependency "activerecord", ">= 5.2"
|
33
|
-
end
|
34
|
-
|
35
|
-
spec.add_dependency "pry" # for using Method#source in MutationTracker
|
36
|
-
spec.add_dependency "activesupport"
|
37
|
-
|
38
|
-
spec.add_development_dependency "sqlite3", ">= 1.3.6"
|
39
|
-
spec.add_development_dependency "database_cleaner"
|
40
|
-
spec.add_development_dependency "bundler", "~> 2.0"
|
41
|
-
spec.add_development_dependency "rake", "~> 13.0"
|
42
|
-
spec.add_development_dependency "rspec", "~> 3.0"
|
43
|
-
spec.add_development_dependency "simplecov", "0.17.1"
|
27
|
+
spec.add_dependency "method_source", "~> 1.0.0"
|
28
|
+
spec.add_dependency "pastel", "~> 0.7"
|
44
29
|
end
|
metadata
CHANGED
@@ -1,141 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tapping_device
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- st0012
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-05-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: method_source
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '5.2'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '5.2'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: pry
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: activesupport
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ">="
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: sqlite3
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: 1.3.6
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: 1.3.6
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: database_cleaner
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
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
16
|
requirements:
|
94
17
|
- - "~>"
|
95
18
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
97
|
-
|
98
|
-
name: rake
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - "~>"
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '13.0'
|
104
|
-
type: :development
|
19
|
+
version: 1.0.0
|
20
|
+
type: :runtime
|
105
21
|
prerelease: false
|
106
22
|
version_requirements: !ruby/object:Gem::Requirement
|
107
23
|
requirements:
|
108
24
|
- - "~>"
|
109
25
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
26
|
+
version: 1.0.0
|
111
27
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
28
|
+
name: pastel
|
113
29
|
requirement: !ruby/object:Gem::Requirement
|
114
30
|
requirements:
|
115
31
|
- - "~>"
|
116
32
|
- !ruby/object:Gem::Version
|
117
|
-
version: '
|
118
|
-
type: :
|
33
|
+
version: '0.7'
|
34
|
+
type: :runtime
|
119
35
|
prerelease: false
|
120
36
|
version_requirements: !ruby/object:Gem::Requirement
|
121
37
|
requirements:
|
122
38
|
- - "~>"
|
123
39
|
- !ruby/object:Gem::Version
|
124
|
-
version: '
|
125
|
-
- !ruby/object:Gem::Dependency
|
126
|
-
name: simplecov
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
128
|
-
requirements:
|
129
|
-
- - '='
|
130
|
-
- !ruby/object:Gem::Version
|
131
|
-
version: 0.17.1
|
132
|
-
type: :development
|
133
|
-
prerelease: false
|
134
|
-
version_requirements: !ruby/object:Gem::Requirement
|
135
|
-
requirements:
|
136
|
-
- - '='
|
137
|
-
- !ruby/object:Gem::Version
|
138
|
-
version: 0.17.1
|
40
|
+
version: '0.7'
|
139
41
|
description: tapping_device lets you understand what your Ruby objects do without
|
140
42
|
digging into the code
|
141
43
|
email:
|
@@ -154,8 +56,8 @@ files:
|
|
154
56
|
- CHANGELOG.md
|
155
57
|
- CODE_OF_CONDUCT.md
|
156
58
|
- Gemfile
|
157
|
-
- Gemfile.lock
|
158
59
|
- LICENSE.txt
|
60
|
+
- Makefile
|
159
61
|
- README.md
|
160
62
|
- Rakefile
|
161
63
|
- bin/console
|
@@ -165,14 +67,12 @@ files:
|
|
165
67
|
- images/print_mutations.png
|
166
68
|
- images/print_traces.png
|
167
69
|
- lib/tapping_device.rb
|
168
|
-
- lib/tapping_device/
|
70
|
+
- lib/tapping_device/configuration.rb
|
169
71
|
- lib/tapping_device/exceptions.rb
|
170
72
|
- lib/tapping_device/manageable.rb
|
171
73
|
- lib/tapping_device/method_hijacker.rb
|
172
74
|
- lib/tapping_device/output.rb
|
173
|
-
- lib/tapping_device/output/
|
174
|
-
- lib/tapping_device/output/payload.rb
|
175
|
-
- lib/tapping_device/output/stdout_writer.rb
|
75
|
+
- lib/tapping_device/output/payload_wrapper.rb
|
176
76
|
- lib/tapping_device/output/writer.rb
|
177
77
|
- lib/tapping_device/payload.rb
|
178
78
|
- lib/tapping_device/trackable.rb
|
@@ -189,7 +89,7 @@ licenses:
|
|
189
89
|
metadata:
|
190
90
|
homepage_uri: https://github.com/st0012/tapping_device
|
191
91
|
source_code_uri: https://github.com/st0012/tapping_device
|
192
|
-
changelog_uri: https://github.com/st0012/tapping_device/
|
92
|
+
changelog_uri: https://github.com/st0012/tapping_device/CHANGELOG.md
|
193
93
|
post_install_message:
|
194
94
|
rdoc_options: []
|
195
95
|
require_paths:
|
@@ -205,7 +105,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
205
105
|
- !ruby/object:Gem::Version
|
206
106
|
version: '0'
|
207
107
|
requirements: []
|
208
|
-
rubygems_version: 3.
|
108
|
+
rubygems_version: 3.2.15
|
209
109
|
signing_key:
|
210
110
|
specification_version: 4
|
211
111
|
summary: tapping_device lets you understand what your Ruby objects do without digging
|
data/Gemfile.lock
DELETED
@@ -1,74 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
tapping_device (0.5.4)
|
5
|
-
activerecord (>= 5.2)
|
6
|
-
activesupport
|
7
|
-
pry
|
8
|
-
|
9
|
-
GEM
|
10
|
-
remote: https://rubygems.org/
|
11
|
-
specs:
|
12
|
-
activemodel (6.0.3.2)
|
13
|
-
activesupport (= 6.0.3.2)
|
14
|
-
activerecord (6.0.3.2)
|
15
|
-
activemodel (= 6.0.3.2)
|
16
|
-
activesupport (= 6.0.3.2)
|
17
|
-
activesupport (6.0.3.2)
|
18
|
-
concurrent-ruby (~> 1.0, >= 1.0.2)
|
19
|
-
i18n (>= 0.7, < 2)
|
20
|
-
minitest (~> 5.1)
|
21
|
-
tzinfo (~> 1.1)
|
22
|
-
zeitwerk (~> 2.2, >= 2.2.2)
|
23
|
-
coderay (1.1.3)
|
24
|
-
concurrent-ruby (1.1.6)
|
25
|
-
database_cleaner (1.7.0)
|
26
|
-
diff-lcs (1.3)
|
27
|
-
docile (1.3.2)
|
28
|
-
i18n (1.8.3)
|
29
|
-
concurrent-ruby (~> 1.0)
|
30
|
-
json (2.3.0)
|
31
|
-
method_source (1.0.0)
|
32
|
-
minitest (5.14.1)
|
33
|
-
pry (0.13.1)
|
34
|
-
coderay (~> 1.1)
|
35
|
-
method_source (~> 1.0)
|
36
|
-
rake (13.0.1)
|
37
|
-
rspec (3.8.0)
|
38
|
-
rspec-core (~> 3.8.0)
|
39
|
-
rspec-expectations (~> 3.8.0)
|
40
|
-
rspec-mocks (~> 3.8.0)
|
41
|
-
rspec-core (3.8.2)
|
42
|
-
rspec-support (~> 3.8.0)
|
43
|
-
rspec-expectations (3.8.4)
|
44
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
45
|
-
rspec-support (~> 3.8.0)
|
46
|
-
rspec-mocks (3.8.1)
|
47
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
48
|
-
rspec-support (~> 3.8.0)
|
49
|
-
rspec-support (3.8.2)
|
50
|
-
simplecov (0.17.1)
|
51
|
-
docile (~> 1.1)
|
52
|
-
json (>= 1.8, < 3)
|
53
|
-
simplecov-html (~> 0.10.0)
|
54
|
-
simplecov-html (0.10.2)
|
55
|
-
sqlite3 (1.4.1)
|
56
|
-
thread_safe (0.3.6)
|
57
|
-
tzinfo (1.2.7)
|
58
|
-
thread_safe (~> 0.1)
|
59
|
-
zeitwerk (2.3.1)
|
60
|
-
|
61
|
-
PLATFORMS
|
62
|
-
ruby
|
63
|
-
|
64
|
-
DEPENDENCIES
|
65
|
-
bundler (~> 2.0)
|
66
|
-
database_cleaner
|
67
|
-
rake (~> 13.0)
|
68
|
-
rspec (~> 3.0)
|
69
|
-
simplecov (= 0.17.1)
|
70
|
-
sqlite3 (>= 1.3.6)
|
71
|
-
tapping_device!
|
72
|
-
|
73
|
-
BUNDLED WITH
|
74
|
-
2.1.1
|
@@ -1,21 +0,0 @@
|
|
1
|
-
class TappingDevice
|
2
|
-
module Output
|
3
|
-
class FileWriter < Writer
|
4
|
-
def initialize(options, output_block)
|
5
|
-
@path = options[:log_file]
|
6
|
-
|
7
|
-
File.write(@path, "") # clean file
|
8
|
-
|
9
|
-
super
|
10
|
-
end
|
11
|
-
|
12
|
-
def write!(payload)
|
13
|
-
output = generate_output(payload)
|
14
|
-
|
15
|
-
File.open(@path, "a") do |f|
|
16
|
-
f << output
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|