tapping_device 0.5.2 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.DS_Store +0 -0
- data/CHANGELOG.md +208 -0
- data/Gemfile.lock +8 -7
- data/README.md +89 -5
- data/lib/tapping_device.rb +29 -22
- data/lib/tapping_device/configurable.rb +25 -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 +175 -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 +1 -2
- data/lib/tapping_device/trackable.rb +46 -17
- data/lib/tapping_device/trackers/mutation_tracker.rb +3 -23
- data/lib/tapping_device/version.rb +1 -1
- data/tapping_device.gemspec +1 -0
- metadata +24 -3
- data/lib/tapping_device/output_payload.rb +0 -173
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9311ddb749d3c7bf09306161da25191c0de47734dea77f645f5d5af62a55d802
|
4
|
+
data.tar.gz: 4df7c9fef5c3a3a2b3852b5b4f4db31ec071149dd19b3fceb1df6f6b1ac191e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: deaece3e6096551df6fe3c7eb492f6049e72b32f7e19b7a8d4067f8d5a6dc9b6fe56a41d3bd9855220ae11c8adcc377cd188f51c043835845be5c2d9250bc6d1
|
7
|
+
data.tar.gz: 057766f277605fb3e80bd2797ee1256f0f6d81fbca56c95226199a72c7e14565dc353292d06711847e257d2f552e9eb3ddcd076ec35f1abd3aaf30cc9f686f5c
|
data/.DS_Store
CHANGED
Binary file
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## [Unreleased](https://github.com/st0012/tapping_device/tree/HEAD)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.2...HEAD)
|
6
|
+
|
7
|
+
**Closed issues:**
|
8
|
+
|
9
|
+
- Support write\_\* helpers [\#44](https://github.com/st0012/tapping_device/issues/44)
|
10
|
+
- Use Method\#source to replace Payload\#method\_head’s implementation [\#19](https://github.com/st0012/tapping_device/issues/19)
|
11
|
+
|
12
|
+
**Merged pull requests:**
|
13
|
+
|
14
|
+
- Support write\_\* helpers [\#47](https://github.com/st0012/tapping_device/pull/47) ([st0012](https://github.com/st0012))
|
15
|
+
- Hijack attr methods with `hijack\_attr\_methods` option [\#45](https://github.com/st0012/tapping_device/pull/45) ([st0012](https://github.com/st0012))
|
16
|
+
|
17
|
+
## [v0.5.2](https://github.com/st0012/tapping_device/tree/v0.5.2) (2020-06-10)
|
18
|
+
|
19
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.1...v0.5.2)
|
20
|
+
|
21
|
+
**Closed issues:**
|
22
|
+
|
23
|
+
- Add print\_mutations [\#41](https://github.com/st0012/tapping_device/issues/41)
|
24
|
+
- Add tap\_on\_mutation! [\#18](https://github.com/st0012/tapping_device/issues/18)
|
25
|
+
|
26
|
+
**Merged pull requests:**
|
27
|
+
|
28
|
+
- Print mutations [\#43](https://github.com/st0012/tapping_device/pull/43) ([st0012](https://github.com/st0012))
|
29
|
+
- Refactorings [\#42](https://github.com/st0012/tapping_device/pull/42) ([st0012](https://github.com/st0012))
|
30
|
+
|
31
|
+
## [v0.5.1](https://github.com/st0012/tapping_device/tree/v0.5.1) (2020-06-07)
|
32
|
+
|
33
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.0...v0.5.1)
|
34
|
+
|
35
|
+
**Fixed bugs:**
|
36
|
+
|
37
|
+
- Filter Out Entries From TappingDevice [\#35](https://github.com/st0012/tapping_device/issues/35)
|
38
|
+
|
39
|
+
**Merged pull requests:**
|
40
|
+
|
41
|
+
- Update GitHub Actions Configuration [\#40](https://github.com/st0012/tapping_device/pull/40) ([st0012](https://github.com/st0012))
|
42
|
+
- Fix typo: Guadian -\> Guardian [\#39](https://github.com/st0012/tapping_device/pull/39) ([skade](https://github.com/skade))
|
43
|
+
- Filter out calls about TappingDevice [\#38](https://github.com/st0012/tapping_device/pull/38) ([st0012](https://github.com/st0012))
|
44
|
+
- Drop tap\_sql! [\#37](https://github.com/st0012/tapping_device/pull/37) ([st0012](https://github.com/st0012))
|
45
|
+
- Add CollectionProxy class [\#36](https://github.com/st0012/tapping_device/pull/36) ([st0012](https://github.com/st0012))
|
46
|
+
|
47
|
+
## [v0.5.0](https://github.com/st0012/tapping_device/tree/v0.5.0) (2020-05-25)
|
48
|
+
|
49
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.11...v0.5.0)
|
50
|
+
|
51
|
+
**Closed issues:**
|
52
|
+
|
53
|
+
- Colorize output of tracing helpers [\#25](https://github.com/st0012/tapping_device/issues/25)
|
54
|
+
|
55
|
+
**Merged pull requests:**
|
56
|
+
|
57
|
+
- Update README.md [\#34](https://github.com/st0012/tapping_device/pull/34) ([st0012](https://github.com/st0012))
|
58
|
+
- Colorize output [\#33](https://github.com/st0012/tapping_device/pull/33) ([st0012](https://github.com/st0012))
|
59
|
+
- Add TappingDevice\#with to register a with condition [\#32](https://github.com/st0012/tapping_device/pull/32) ([st0012](https://github.com/st0012))
|
60
|
+
|
61
|
+
## [v0.4.11](https://github.com/st0012/tapping_device/tree/v0.4.11) (2020-04-19)
|
62
|
+
|
63
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.10...v0.4.11)
|
64
|
+
|
65
|
+
**Merged pull requests:**
|
66
|
+
|
67
|
+
- Update rake requirement from ~\> 10.0 to ~\> 13.0 [\#31](https://github.com/st0012/tapping_device/pull/31) ([dependabot[bot]](https://github.com/apps/dependabot))
|
68
|
+
|
69
|
+
## [v0.4.10](https://github.com/st0012/tapping_device/tree/v0.4.10) (2020-02-05)
|
70
|
+
|
71
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.9...v0.4.10)
|
72
|
+
|
73
|
+
**Implemented enhancements:**
|
74
|
+
|
75
|
+
- Usability improvements [\#30](https://github.com/st0012/tapping_device/pull/30) ([st0012](https://github.com/st0012))
|
76
|
+
|
77
|
+
**Merged pull requests:**
|
78
|
+
|
79
|
+
- Fix tap\_init!'s payload content [\#29](https://github.com/st0012/tapping_device/pull/29) ([st0012](https://github.com/st0012))
|
80
|
+
- Refactorings and fixes [\#28](https://github.com/st0012/tapping_device/pull/28) ([st0012](https://github.com/st0012))
|
81
|
+
|
82
|
+
## [v0.4.9](https://github.com/st0012/tapping_device/tree/v0.4.9) (2020-01-20)
|
83
|
+
|
84
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.8...v0.4.9)
|
85
|
+
|
86
|
+
**Implemented enhancements:**
|
87
|
+
|
88
|
+
- Improve detail\_call\_info's output format [\#27](https://github.com/st0012/tapping_device/pull/27) ([st0012](https://github.com/st0012))
|
89
|
+
|
90
|
+
## [v0.4.8](https://github.com/st0012/tapping_device/tree/v0.4.8) (2020-01-05)
|
91
|
+
|
92
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.7...v0.4.8)
|
93
|
+
|
94
|
+
**Closed issues:**
|
95
|
+
|
96
|
+
- Provide options for tapping on call or return events [\#23](https://github.com/st0012/tapping_device/issues/23)
|
97
|
+
|
98
|
+
**Merged pull requests:**
|
99
|
+
|
100
|
+
- Add tracing helpers [\#24](https://github.com/st0012/tapping_device/pull/24) ([st0012](https://github.com/st0012))
|
101
|
+
|
102
|
+
## [v0.4.7](https://github.com/st0012/tapping_device/tree/v0.4.7) (2019-12-29)
|
103
|
+
|
104
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.6...v0.4.7)
|
105
|
+
|
106
|
+
**Implemented enhancements:**
|
107
|
+
|
108
|
+
- Config test coverage for codeclimate [\#22](https://github.com/st0012/tapping_device/pull/22) ([st0012](https://github.com/st0012))
|
109
|
+
|
110
|
+
**Closed issues:**
|
111
|
+
|
112
|
+
- Support tracking ActiveRecord::Base instances by their ids [\#17](https://github.com/st0012/tapping_device/issues/17)
|
113
|
+
|
114
|
+
**Merged pull requests:**
|
115
|
+
|
116
|
+
- Refactor tests and some minor fixes [\#21](https://github.com/st0012/tapping_device/pull/21) ([st0012](https://github.com/st0012))
|
117
|
+
- Support track\_as\_records option [\#20](https://github.com/st0012/tapping_device/pull/20) ([st0012](https://github.com/st0012))
|
118
|
+
|
119
|
+
## [v0.4.6](https://github.com/st0012/tapping_device/tree/v0.4.6) (2019-12-25)
|
120
|
+
|
121
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.5...v0.4.6)
|
122
|
+
|
123
|
+
**Merged pull requests:**
|
124
|
+
|
125
|
+
- Add TappingDevice\#and\_print method [\#16](https://github.com/st0012/tapping_device/pull/16) ([st0012](https://github.com/st0012))
|
126
|
+
- Add Payload\#detail\_call\_info and improve method\_name's format [\#15](https://github.com/st0012/tapping_device/pull/15) ([st0012](https://github.com/st0012))
|
127
|
+
|
128
|
+
## [v0.4.5](https://github.com/st0012/tapping_device/tree/v0.4.5) (2019-12-15)
|
129
|
+
|
130
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.4...v0.4.5)
|
131
|
+
|
132
|
+
## [v0.4.4](https://github.com/st0012/tapping_device/tree/v0.4.4) (2019-12-15)
|
133
|
+
|
134
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.3...v0.4.4)
|
135
|
+
|
136
|
+
**Merged pull requests:**
|
137
|
+
|
138
|
+
- Implement tap\_passed! [\#14](https://github.com/st0012/tapping_device/pull/14) ([st0012](https://github.com/st0012))
|
139
|
+
|
140
|
+
## [v0.4.3](https://github.com/st0012/tapping_device/tree/v0.4.3) (2019-12-09)
|
141
|
+
|
142
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.2...v0.4.3)
|
143
|
+
|
144
|
+
## [v0.4.2](https://github.com/st0012/tapping_device/tree/v0.4.2) (2019-12-09)
|
145
|
+
|
146
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.1...v0.4.2)
|
147
|
+
|
148
|
+
**Merged pull requests:**
|
149
|
+
|
150
|
+
- Refactor tap\_sql! [\#13](https://github.com/st0012/tapping_device/pull/13) ([st0012](https://github.com/st0012))
|
151
|
+
- Improve tap sql [\#12](https://github.com/st0012/tapping_device/pull/12) ([st0012](https://github.com/st0012))
|
152
|
+
|
153
|
+
## [v0.4.1](https://github.com/st0012/tapping_device/tree/v0.4.1) (2019-12-06)
|
154
|
+
|
155
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.0...v0.4.1)
|
156
|
+
|
157
|
+
**Merged pull requests:**
|
158
|
+
|
159
|
+
- Add TappingDevice::Payload class [\#11](https://github.com/st0012/tapping_device/pull/11) ([st0012](https://github.com/st0012))
|
160
|
+
|
161
|
+
## [v0.4.0](https://github.com/st0012/tapping_device/tree/v0.4.0) (2019-11-25)
|
162
|
+
|
163
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.3.0...v0.4.0)
|
164
|
+
|
165
|
+
**Merged pull requests:**
|
166
|
+
|
167
|
+
- Support tap\_sql! [\#10](https://github.com/st0012/tapping_device/pull/10) ([st0012](https://github.com/st0012))
|
168
|
+
- Minor adjustment [\#9](https://github.com/st0012/tapping_device/pull/9) ([NickWarm](https://github.com/NickWarm))
|
169
|
+
|
170
|
+
## [v0.3.0](https://github.com/st0012/tapping_device/tree/v0.3.0) (2019-11-03)
|
171
|
+
|
172
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.2.0...v0.3.0)
|
173
|
+
|
174
|
+
**Implemented enhancements:**
|
175
|
+
|
176
|
+
- Largely improve performance by fixing bad design [\#8](https://github.com/st0012/tapping_device/pull/8) ([st0012](https://github.com/st0012))
|
177
|
+
|
178
|
+
## [v0.2.0](https://github.com/st0012/tapping_device/tree/v0.2.0) (2019-11-02)
|
179
|
+
|
180
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.1.1...v0.2.0)
|
181
|
+
|
182
|
+
**Implemented enhancements:**
|
183
|
+
|
184
|
+
- Add Device class [\#3](https://github.com/st0012/tapping_device/pull/3) ([st0012](https://github.com/st0012))
|
185
|
+
|
186
|
+
**Merged pull requests:**
|
187
|
+
|
188
|
+
- Remove tapping\_deivce/device.rb [\#7](https://github.com/st0012/tapping_device/pull/7) ([st0012](https://github.com/st0012))
|
189
|
+
- Reduce namespace [\#6](https://github.com/st0012/tapping_device/pull/6) ([st0012](https://github.com/st0012))
|
190
|
+
- Register and control all devices from Device class [\#5](https://github.com/st0012/tapping_device/pull/5) ([st0012](https://github.com/st0012))
|
191
|
+
- Support `TappingDevice::Device\#stop\_when` [\#4](https://github.com/st0012/tapping_device/pull/4) ([st0012](https://github.com/st0012))
|
192
|
+
|
193
|
+
## [v0.1.1](https://github.com/st0012/tapping_device/tree/v0.1.1) (2019-10-20)
|
194
|
+
|
195
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.1.0...v0.1.1)
|
196
|
+
|
197
|
+
**Implemented enhancements:**
|
198
|
+
|
199
|
+
- More filters, Refactoring and Readme update [\#2](https://github.com/st0012/tapping_device/pull/2) ([st0012](https://github.com/st0012))
|
200
|
+
- Support tapping ActiveRecord class/instance [\#1](https://github.com/st0012/tapping_device/pull/1) ([st0012](https://github.com/st0012))
|
201
|
+
|
202
|
+
## [v0.1.0](https://github.com/st0012/tapping_device/tree/v0.1.0) (2019-10-19)
|
203
|
+
|
204
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/61039c87a55c664661b788e24311e263b28a3ee8...v0.1.0)
|
205
|
+
|
206
|
+
|
207
|
+
|
208
|
+
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
|
data/Gemfile.lock
CHANGED
@@ -1,19 +1,20 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
tapping_device (0.5.
|
4
|
+
tapping_device (0.5.3)
|
5
5
|
activerecord (>= 5.2)
|
6
|
+
activesupport
|
6
7
|
pry
|
7
8
|
|
8
9
|
GEM
|
9
10
|
remote: https://rubygems.org/
|
10
11
|
specs:
|
11
|
-
activemodel (6.0.3.
|
12
|
-
activesupport (= 6.0.3.
|
13
|
-
activerecord (6.0.3.
|
14
|
-
activemodel (= 6.0.3.
|
15
|
-
activesupport (= 6.0.3.
|
16
|
-
activesupport (6.0.3.
|
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)
|
17
18
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
18
19
|
i18n (>= 0.7, < 2)
|
19
20
|
minitest (~> 5.1)
|
data/README.md
CHANGED
@@ -174,6 +174,20 @@ Now you can see what method changes which states. And more importantly, you get
|
|
174
174
|
|
175
175
|
**You can try these examples on [my fork of discourse](https://github.com/st0012/discourse/tree/demo-for-tapping-device)**
|
176
176
|
|
177
|
+
### `write_*` helpers
|
178
|
+
|
179
|
+
`tapping_device` also provides helpers that write the events into files:
|
180
|
+
|
181
|
+
- `write_calls(object)`
|
182
|
+
- `write_traces(object)`
|
183
|
+
- `write_mutations(object)`
|
184
|
+
|
185
|
+
The default destination is `/tmp/tapping_device.log`. You can change it with the `log_file` option:
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
write_calls(object, log_file: "/tmp/another_file")
|
189
|
+
```
|
190
|
+
|
177
191
|
|
178
192
|
## Installation
|
179
193
|
Add this line to your application's Gemfile:
|
@@ -197,9 +211,9 @@ $ gem install tapping_device
|
|
197
211
|
**Depending on the size of your application, `TappingDevice` could harm the performance significantly. So make sure you don't put it inside the production group**
|
198
212
|
|
199
213
|
|
200
|
-
|
214
|
+
## Advance Usages & Options
|
201
215
|
|
202
|
-
|
216
|
+
### Add Conditions With `.with`
|
203
217
|
|
204
218
|
Sometimes we don't need to know all the calls or traces of an object; we just want some of them. In those cases, we can chain the helpers with `.with` to filter the calls/traces.
|
205
219
|
|
@@ -210,7 +224,29 @@ print_calls(object).with do |payload|
|
|
210
224
|
end
|
211
225
|
```
|
212
226
|
|
213
|
-
|
227
|
+
### Options
|
228
|
+
|
229
|
+
There are many options you can pass when using a helper method. You can list all available options and their default value with
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
TappingDevice::Configurable::DEFAULTS #=> {
|
233
|
+
:filter_by_paths=>[],
|
234
|
+
:exclude_by_paths=>[],
|
235
|
+
:with_trace_to=>50,
|
236
|
+
:event_type=>:return,
|
237
|
+
:hijack_attr_methods=>false,
|
238
|
+
:track_as_records=>false,
|
239
|
+
:inspect=>false,
|
240
|
+
:colorize=>true,
|
241
|
+
:log_file=>"/tmp/tapping_device.log"
|
242
|
+
}
|
243
|
+
```
|
244
|
+
|
245
|
+
Here are some commonly used options:
|
246
|
+
|
247
|
+
#### `colorize: false`
|
248
|
+
|
249
|
+
- default: `true`
|
214
250
|
|
215
251
|
By default `print_calls` and `print_traces` colorize their output. If you don't want the colors, you can use `colorize: false` to disable it.
|
216
252
|
|
@@ -220,7 +256,9 @@ print_calls(object, colorize: false)
|
|
220
256
|
```
|
221
257
|
|
222
258
|
|
223
|
-
#### `inspect: true`
|
259
|
+
#### `inspect: true`
|
260
|
+
|
261
|
+
- default: `false`
|
224
262
|
|
225
263
|
As you might have noticed, all the objects are converted into strings with `#to_s` instead of `#inspect`. This is because when used on some Rails objects, `#inspect` can generate a significantly larger string than `#to_s`. For example:
|
226
264
|
|
@@ -229,6 +267,53 @@ post.to_s #=> #
|
|
229
267
|
post.inspect #=> #<Post id: 649, user_id: 3, topic_id: 600, post_number: 1, raw: "Hello world", cooked: "<p>Hello world</p>", created_at: "2020-05-24 08:07:29", updated_at: "2020-05-24 08:07:29", reply_to_post_number: nil, reply_count: 0, quote_count: 0, deleted_at: nil, off_topic_count: 0, like_count: 0, incoming_link_count: 0, bookmark_count: 0, score: nil, reads: 0, post_type: 1, sort_order: 1, last_editor_id: 3, hidden: false, hidden_reason_id: nil, notify_moderators_count: 0, spam_count: 0, illegal_count: 0, inappropriate_count: 0, last_version_at: "2020-05-24 08:07:29", user_deleted: false, reply_to_user_id: nil, percent_rank: 1.0, notify_user_count: 0, like_score: 0, deleted_by_id: nil, edit_reason: nil, word_count: 2, version: 1, cook_method: 1, wiki: false, baked_at: "2020-05-24 08:07:29", baked_version: 2, hidden_at: nil, self_edits: 0, reply_quoted: false, via_email: false, raw_email: nil, public_version: 1, action_code: nil, image_url: nil, locked_by_id: nil, image_upload_id: nil>
|
230
268
|
```
|
231
269
|
|
270
|
+
#### `hijack_attr_methods: true`
|
271
|
+
|
272
|
+
- default: `false`
|
273
|
+
- except for `tap_mutation!` and `print_mutations`
|
274
|
+
|
275
|
+
Because `TracePoint` doesn't track methods generated by `attr_*` helpers (see [this issue](https://bugs.ruby-lang.org/issues/16383) for more info), we need to redefine those methods with the normal method definition.
|
276
|
+
|
277
|
+
For example, it generates
|
278
|
+
|
279
|
+
```ruby
|
280
|
+
def name=(val)
|
281
|
+
@name = val
|
282
|
+
end
|
283
|
+
```
|
284
|
+
|
285
|
+
for
|
286
|
+
|
287
|
+
```ruby
|
288
|
+
attr_writer :name
|
289
|
+
```
|
290
|
+
|
291
|
+
This hack will only be applied to the target instance with `instance_eval`. So other instances of the class remain untouched.
|
292
|
+
|
293
|
+
The default is `false` because
|
294
|
+
|
295
|
+
1. Checking what methods are generated by `attr_*` helpers isn't free. It's an `O(n)` operation, where `n` is the number of methods the target object has.
|
296
|
+
2. It's still unclear if this hack safe enough for most applications.
|
297
|
+
|
298
|
+
|
299
|
+
### Global Configuration
|
300
|
+
|
301
|
+
If you don't want to pass options every time you use a helper, you can use global configuration to change the default values:
|
302
|
+
|
303
|
+
```ruby
|
304
|
+
TappingDevice.config[:colorize] = false
|
305
|
+
TappingDevice.config[:hijack_attr_methods] = true
|
306
|
+
```
|
307
|
+
|
308
|
+
And if you're using Rails, you can put the configs under `config/initializers/tapping_device.rb` like this:
|
309
|
+
|
310
|
+
```ruby
|
311
|
+
if defined?(TappingDevice)
|
312
|
+
TappingDevice.config[:colorize] = false
|
313
|
+
TappingDevice.config[:hijack_attr_methods] = true
|
314
|
+
end
|
315
|
+
```
|
316
|
+
|
232
317
|
|
233
318
|
### Lower-Level Helpers
|
234
319
|
`print_calls` and `print_traces` aren't the only helpers you can get from `TappingDevice`. They are actually built on top of other helpers, which you can use as well. To know more about them, please check [this page](https://github.com/st0012/tapping_device/wiki/Advance-Usages)
|
@@ -256,4 +341,3 @@ The gem is available as open-source under the terms of the [MIT License](https:/
|
|
256
341
|
## Code of Conduct
|
257
342
|
|
258
343
|
Everyone interacting in the TappingDevice project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/tapping_device/blob/master/CODE_OF_CONDUCT.md).
|
259
|
-
|
data/lib/tapping_device.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
1
|
require "active_record"
|
2
|
+
require "active_support/core_ext/module/introspection"
|
3
|
+
require "pry" # for using Method#source
|
4
|
+
|
2
5
|
require "tapping_device/version"
|
3
6
|
require "tapping_device/manageable"
|
4
7
|
require "tapping_device/payload"
|
5
|
-
require "tapping_device/
|
8
|
+
require "tapping_device/output"
|
6
9
|
require "tapping_device/trackable"
|
10
|
+
require "tapping_device/configurable"
|
7
11
|
require "tapping_device/exceptions"
|
12
|
+
require "tapping_device/method_hijacker"
|
8
13
|
require "tapping_device/trackers/initialization_tracker"
|
9
14
|
require "tapping_device/trackers/passed_tracker"
|
10
15
|
require "tapping_device/trackers/association_call_tracker"
|
@@ -23,6 +28,9 @@ class TappingDevice
|
|
23
28
|
|
24
29
|
extend Manageable
|
25
30
|
|
31
|
+
include Configurable
|
32
|
+
include Output::Helpers
|
33
|
+
|
26
34
|
def initialize(options = {}, &block)
|
27
35
|
@block = block
|
28
36
|
@output_block = nil
|
@@ -33,19 +41,6 @@ class TappingDevice
|
|
33
41
|
TappingDevice.devices << self
|
34
42
|
end
|
35
43
|
|
36
|
-
def and_print(payload_method = nil, &block)
|
37
|
-
@output_block =
|
38
|
-
if block
|
39
|
-
-> (output_payload) { puts(block.call(output_payload)) }
|
40
|
-
elsif payload_method
|
41
|
-
-> (output_payload) { puts(output_payload.send(payload_method)) }
|
42
|
-
else
|
43
|
-
raise "need to provide either a payload method name or a block"
|
44
|
-
end
|
45
|
-
|
46
|
-
self
|
47
|
-
end
|
48
|
-
|
49
44
|
def with(&block)
|
50
45
|
@with_condition = block
|
51
46
|
end
|
@@ -83,6 +78,8 @@ class TappingDevice
|
|
83
78
|
@target = object
|
84
79
|
validate_target!
|
85
80
|
|
81
|
+
MethodHijacker.new(@target).hijack_methods! if options[:hijack_attr_methods]
|
82
|
+
|
86
83
|
@trace_point = build_minimum_trace_point(event_type: options[:event_type]) do |payload|
|
87
84
|
record_call!(payload)
|
88
85
|
|
@@ -130,7 +127,7 @@ class TappingDevice
|
|
130
127
|
|
131
128
|
if Module.respond_to?(:module_parents)
|
132
129
|
tp.defined_class.module_parents.include?(TappingDevice)
|
133
|
-
|
130
|
+
elsif Module.respond_to?(:parents)
|
134
131
|
tp.defined_class.parents.include?(TappingDevice)
|
135
132
|
end
|
136
133
|
end
|
@@ -201,13 +198,15 @@ class TappingDevice
|
|
201
198
|
end
|
202
199
|
|
203
200
|
def process_options(options)
|
204
|
-
options[:filter_by_paths] ||= []
|
205
|
-
options[:exclude_by_paths] ||= []
|
206
|
-
options[:with_trace_to] ||=
|
207
|
-
options[:
|
208
|
-
options[:
|
201
|
+
options[:filter_by_paths] ||= config[:filter_by_paths]
|
202
|
+
options[:exclude_by_paths] ||= config[:exclude_by_paths]
|
203
|
+
options[:with_trace_to] ||= config[:with_trace_to]
|
204
|
+
options[:event_type] ||= config[:event_type]
|
205
|
+
options[:hijack_attr_methods] ||= config[:hijack_attr_methods]
|
206
|
+
options[:track_as_records] ||= config[:track_as_records]
|
207
|
+
|
209
208
|
options[:descendants] ||= []
|
210
|
-
options[:
|
209
|
+
options[:root_device] ||= self
|
211
210
|
options
|
212
211
|
end
|
213
212
|
|
@@ -227,7 +226,7 @@ class TappingDevice
|
|
227
226
|
def record_call!(payload)
|
228
227
|
return if @disabled
|
229
228
|
|
230
|
-
|
229
|
+
write_output!(payload) if @output_writer
|
231
230
|
|
232
231
|
if @block
|
233
232
|
root_device.calls << @block.call(payload)
|
@@ -236,10 +235,18 @@ class TappingDevice
|
|
236
235
|
end
|
237
236
|
end
|
238
237
|
|
238
|
+
def write_output!(payload)
|
239
|
+
@output_writer.write!(payload)
|
240
|
+
end
|
241
|
+
|
239
242
|
def stop_if_condition_fulfilled!(payload)
|
240
243
|
if @stop_when&.call(payload)
|
241
244
|
stop!
|
242
245
|
root_device.stop!
|
243
246
|
end
|
244
247
|
end
|
248
|
+
|
249
|
+
def config
|
250
|
+
TappingDevice.config
|
251
|
+
end
|
245
252
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "active_support/configurable"
|
2
|
+
require "active_support/concern"
|
3
|
+
|
4
|
+
class TappingDevice
|
5
|
+
module Configurable
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
DEFAULTS = {
|
9
|
+
filter_by_paths: [],
|
10
|
+
exclude_by_paths: [],
|
11
|
+
with_trace_to: 50,
|
12
|
+
event_type: :return,
|
13
|
+
hijack_attr_methods: false,
|
14
|
+
track_as_records: false,
|
15
|
+
}.merge(TappingDevice::Output::DEFAULT_OPTIONS)
|
16
|
+
|
17
|
+
included do
|
18
|
+
include ActiveSupport::Configurable
|
19
|
+
|
20
|
+
DEFAULTS.each do |key, value|
|
21
|
+
config[key] = value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
class TappingDevice
|
2
|
+
class MethodHijacker
|
3
|
+
attr_reader :target
|
4
|
+
|
5
|
+
def initialize(target)
|
6
|
+
@target = target
|
7
|
+
end
|
8
|
+
|
9
|
+
def hijack_methods!
|
10
|
+
target.methods.each do |method_name|
|
11
|
+
if is_writer_method?(method_name)
|
12
|
+
redefine_writer_method!(method_name)
|
13
|
+
elsif is_reader_method?(method_name)
|
14
|
+
redefine_reader_method!(method_name)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def is_writer_method?(method_name)
|
22
|
+
has_definition_source?(method_name) && method_name.match?(/\w+=/) && target.method(method_name).source.match?(/attr_writer|attr_accessor/)
|
23
|
+
end
|
24
|
+
|
25
|
+
def is_reader_method?(method_name)
|
26
|
+
has_definition_source?(method_name) && target.method(method_name).source.match?(/attr_reader|attr_accessor/)
|
27
|
+
end
|
28
|
+
|
29
|
+
def has_definition_source?(method_name)
|
30
|
+
target.method(method_name).source_location
|
31
|
+
end
|
32
|
+
|
33
|
+
def redefine_writer_method!(method_name)
|
34
|
+
ivar_name = "@#{method_name.to_s.sub("=", "")}"
|
35
|
+
|
36
|
+
target.instance_eval <<-RUBY, __FILE__, __LINE__ + 1
|
37
|
+
def #{method_name}(val)
|
38
|
+
#{ivar_name} = val
|
39
|
+
end
|
40
|
+
RUBY
|
41
|
+
end
|
42
|
+
|
43
|
+
def redefine_reader_method!(method_name)
|
44
|
+
target.instance_eval <<-RUBY, __FILE__, __LINE__ + 1
|
45
|
+
def #{method_name}
|
46
|
+
@#{method_name}
|
47
|
+
end
|
48
|
+
RUBY
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "tapping_device/output/payload"
|
2
|
+
require "tapping_device/output/writer"
|
3
|
+
require "tapping_device/output/stdout_writer"
|
4
|
+
require "tapping_device/output/file_writer"
|
5
|
+
|
6
|
+
class TappingDevice
|
7
|
+
module Output
|
8
|
+
DEFAULT_OPTIONS = {
|
9
|
+
inspect: false,
|
10
|
+
colorize: true,
|
11
|
+
log_file: "/tmp/tapping_device.log"
|
12
|
+
}
|
13
|
+
|
14
|
+
module Helpers
|
15
|
+
def and_write(payload_method = nil, options: {}, &block)
|
16
|
+
and_output(payload_method, options: options, writer_klass: FileWriter, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def and_print(payload_method = nil, options: {}, &block)
|
20
|
+
and_output(payload_method, options: options, writer_klass: StdoutWriter, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def and_output(payload_method = nil, options: {}, writer_klass:, &block)
|
24
|
+
output_block = generate_output_block(payload_method, block)
|
25
|
+
@output_writer = writer_klass.new(options, output_block)
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def generate_output_block(payload_method, block)
|
32
|
+
if block
|
33
|
+
block
|
34
|
+
elsif payload_method
|
35
|
+
-> (output_payload, output_options) { output_payload.send(payload_method, output_options) }
|
36
|
+
else
|
37
|
+
raise "need to provide either a payload method name or a block"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,21 @@
|
|
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
|
@@ -0,0 +1,175 @@
|
|
1
|
+
class TappingDevice
|
2
|
+
module Output
|
3
|
+
class Payload < Payload
|
4
|
+
UNDEFINED = "[undefined]"
|
5
|
+
|
6
|
+
alias :raw_arguments :arguments
|
7
|
+
alias :raw_return_value :return_value
|
8
|
+
|
9
|
+
def method_name(options = {})
|
10
|
+
":#{super(options)}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def arguments(options = {})
|
14
|
+
generate_string_result(raw_arguments, options[:inspect])
|
15
|
+
end
|
16
|
+
|
17
|
+
def return_value(options = {})
|
18
|
+
generate_string_result(raw_return_value, options[:inspect])
|
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
|
+
return_value: {symbol: "=>", color: COLORS[:megenta]},
|
41
|
+
arguments: {symbol: "<=", color: COLORS[:orange]},
|
42
|
+
ivar_changes: {symbol: "changes:\n", color: COLORS[:blue]},
|
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)}\n"
|
88
|
+
msg += " > #{method_head}\n" 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
|
+
def ivar_changes(options = {})
|
103
|
+
super.map do |ivar, value_changes|
|
104
|
+
before = generate_string_result(value_changes[:before], options[:inspect])
|
105
|
+
after = generate_string_result(value_changes[:after], options[:inspect])
|
106
|
+
|
107
|
+
if options[:colorize]
|
108
|
+
ivar = "#{COLORS[:orange]}#{ivar}#{COLORS[:reset]}"
|
109
|
+
before = "#{COLORS[:blue]}#{before.to_s}#{COLORS[:reset]}"
|
110
|
+
after = "#{COLORS[:blue]}#{after.to_s}#{COLORS[:reset]}"
|
111
|
+
end
|
112
|
+
|
113
|
+
" #{ivar}: #{before.to_s} => #{after.to_s}"
|
114
|
+
end.join("\n")
|
115
|
+
end
|
116
|
+
|
117
|
+
def call_info_with_ivar_changes(options = {})
|
118
|
+
<<~MSG
|
119
|
+
#{method_name_and_defined_class(options)}
|
120
|
+
from: #{location(options)}
|
121
|
+
changes:
|
122
|
+
#{ivar_changes(options)}
|
123
|
+
|
124
|
+
MSG
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def value_with_color(value, color)
|
130
|
+
"#{COLORS[color]}#{value}#{COLORS[:reset]}"
|
131
|
+
end
|
132
|
+
|
133
|
+
def generate_string_result(obj, inspect)
|
134
|
+
case obj
|
135
|
+
when Array
|
136
|
+
array_to_string(obj, inspect)
|
137
|
+
when Hash
|
138
|
+
hash_to_string(obj, inspect)
|
139
|
+
when UNDEFINED
|
140
|
+
UNDEFINED
|
141
|
+
when String
|
142
|
+
"\"#{obj}\""
|
143
|
+
when nil
|
144
|
+
"nil"
|
145
|
+
else
|
146
|
+
inspect ? obj.inspect : obj.to_s
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def array_to_string(array, inspect)
|
151
|
+
elements_string = array.map do |elem|
|
152
|
+
generate_string_result(elem, inspect)
|
153
|
+
end.join(", ")
|
154
|
+
"[#{elements_string}]"
|
155
|
+
end
|
156
|
+
|
157
|
+
def hash_to_string(hash, inspect)
|
158
|
+
elements_string = hash.map do |key, value|
|
159
|
+
"#{key.to_s}: #{generate_string_result(value, inspect)}"
|
160
|
+
end.join(", ")
|
161
|
+
"{#{elements_string}}"
|
162
|
+
end
|
163
|
+
|
164
|
+
def obj_to_string(element, inspect)
|
165
|
+
to_string_method = inspect ? :inspect : :to_s
|
166
|
+
|
167
|
+
if !inspect && element.is_a?(String)
|
168
|
+
"\"#{element}\""
|
169
|
+
else
|
170
|
+
element.send(to_string_method)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class TappingDevice
|
2
|
+
module Output
|
3
|
+
class Writer
|
4
|
+
def initialize(options, output_block)
|
5
|
+
@options = options
|
6
|
+
@output_block = output_block
|
7
|
+
end
|
8
|
+
|
9
|
+
def write!(payload)
|
10
|
+
raise NotImplementedError
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def generate_output(payload)
|
16
|
+
@output_block.call(Output::Payload.init(payload), @options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -21,38 +21,67 @@ class TappingDevice
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def print_traces(target, options = {})
|
24
|
-
|
25
|
-
|
24
|
+
output_traces(target, options, output_action: :and_print)
|
25
|
+
end
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
end
|
30
|
-
device_2 = tap_passed!(target, options).and_print do |output_payload|
|
31
|
-
output_payload.passed_at(output_options)
|
32
|
-
end
|
33
|
-
CollectionProxy.new([device_1, device_2])
|
27
|
+
def write_traces(target, options = {})
|
28
|
+
output_traces(target, options, output_action: :and_write)
|
34
29
|
end
|
35
30
|
|
36
31
|
def print_calls(target, options = {})
|
37
|
-
|
32
|
+
output_calls(target, options, output_action: :and_print)
|
33
|
+
end
|
34
|
+
|
35
|
+
def write_calls(target, options = {})
|
36
|
+
output_calls(target, options, output_action: :and_write)
|
37
|
+
end
|
38
|
+
|
39
|
+
def print_mutations(target, options = {})
|
40
|
+
output_mutations(target, options, output_action: :and_print)
|
41
|
+
end
|
42
|
+
|
43
|
+
def write_mutations(target, options = {})
|
44
|
+
output_mutations(target, options, output_action: :and_write)
|
45
|
+
end
|
38
46
|
|
39
|
-
|
47
|
+
private
|
48
|
+
|
49
|
+
def output_calls(target, options = {}, output_action:)
|
50
|
+
device_options, output_options = separate_options(options)
|
51
|
+
|
52
|
+
tap_on!(target, device_options).send(output_action, options: output_options) do |output_payload, output_options|
|
40
53
|
output_payload.detail_call_info(output_options)
|
41
54
|
end
|
42
55
|
end
|
43
56
|
|
44
|
-
def
|
45
|
-
output_options =
|
57
|
+
def output_traces(target, options = {}, output_action:)
|
58
|
+
device_options, output_options = separate_options(options)
|
59
|
+
device_options[:event_type] = :call
|
46
60
|
|
47
|
-
|
61
|
+
device_1 = tap_on!(target, device_options).send(output_action, options: output_options) do |output_payload, output_options|
|
62
|
+
"Called #{output_payload.method_name_and_location(output_options)}\n"
|
63
|
+
end
|
64
|
+
device_2 = tap_passed!(target, device_options).send(output_action, options: output_options) do |output_payload, output_options|
|
65
|
+
output_payload.passed_at(output_options)
|
66
|
+
end
|
67
|
+
CollectionProxy.new([device_1, device_2])
|
68
|
+
end
|
69
|
+
|
70
|
+
def output_mutations(target, options = {}, output_action:)
|
71
|
+
device_options, output_options = separate_options(options)
|
72
|
+
|
73
|
+
tap_mutation!(target, device_options).send(output_action, options: output_options) do |output_payload, output_options|
|
48
74
|
output_payload.call_info_with_ivar_changes(output_options)
|
49
75
|
end
|
50
76
|
end
|
51
77
|
|
52
|
-
|
78
|
+
def separate_options(options)
|
79
|
+
output_options = Output::DEFAULT_OPTIONS.keys.each_with_object({}) do |key, hash|
|
80
|
+
hash[key] = options.fetch(key, TappingDevice.config[key])
|
81
|
+
options.delete(key)
|
82
|
+
end
|
53
83
|
|
54
|
-
|
55
|
-
{inspect: options.delete(:inspect), colorize: options.fetch(:colorize, true)}
|
84
|
+
[options, output_options]
|
56
85
|
end
|
57
86
|
|
58
87
|
class CollectionProxy
|
@@ -1,16 +1,14 @@
|
|
1
|
-
require "pry" # for using Method#source
|
2
|
-
|
3
1
|
class TappingDevice
|
4
2
|
module Trackers
|
5
3
|
class MutationTracker < TappingDevice
|
6
4
|
def initialize(options, &block)
|
5
|
+
options[:hijack_attr_methods] = true
|
7
6
|
super
|
8
7
|
@snapshot_stack = []
|
9
8
|
end
|
10
9
|
|
11
10
|
def track(object)
|
12
11
|
super
|
13
|
-
hijack_attr_writers
|
14
12
|
insert_snapshot_taking_trace_point
|
15
13
|
self
|
16
14
|
end
|
@@ -45,24 +43,6 @@ class TappingDevice
|
|
45
43
|
end
|
46
44
|
end
|
47
45
|
|
48
|
-
def hijack_attr_writers
|
49
|
-
writer_methods = target.methods.grep(/\w+=/)
|
50
|
-
writer_methods.each do |method_name|
|
51
|
-
if target.method(method_name).source.match?(/attr_writer|attr_accessor/)
|
52
|
-
ivar_name = "@#{method_name.to_s.sub("=", "")}"
|
53
|
-
|
54
|
-
# need to use instance_eval to make the call site location consistent with normal methods
|
55
|
-
target.instance_eval(
|
56
|
-
<<~CODE
|
57
|
-
def #{method_name}(val)
|
58
|
-
#{ivar_name} = val
|
59
|
-
end
|
60
|
-
CODE
|
61
|
-
)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
46
|
def build_payload(tp:, filepath:, line_number:)
|
67
47
|
payload = super
|
68
48
|
|
@@ -78,12 +58,12 @@ class TappingDevice
|
|
78
58
|
|
79
59
|
additional_keys = @latest_instance_variables.keys - @instance_variables_snapshot.keys
|
80
60
|
additional_keys.each do |key|
|
81
|
-
changes[key] = {before:
|
61
|
+
changes[key] = {before: Output::Payload::UNDEFINED, after: @latest_instance_variables[key]}
|
82
62
|
end
|
83
63
|
|
84
64
|
removed_keys = @instance_variables_snapshot.keys - @latest_instance_variables.keys
|
85
65
|
removed_keys.each do |key|
|
86
|
-
changes[key] = {before: @instance_variables_snapshot[key], after:
|
66
|
+
changes[key] = {before: @instance_variables_snapshot[key], after: Output::Payload::UNDEFINED}
|
87
67
|
end
|
88
68
|
|
89
69
|
remained_keys = @latest_instance_variables.keys - additional_keys
|
data/tapping_device.gemspec
CHANGED
@@ -33,6 +33,7 @@ Gem::Specification.new do |spec|
|
|
33
33
|
end
|
34
34
|
|
35
35
|
spec.add_dependency "pry" # for using Method#source in MutationTracker
|
36
|
+
spec.add_dependency "activesupport"
|
36
37
|
|
37
38
|
spec.add_development_dependency "sqlite3", ">= 1.3.6"
|
38
39
|
spec.add_development_dependency "database_cleaner"
|
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.3
|
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-
|
11
|
+
date: 2020-06-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
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'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: sqlite3
|
43
57
|
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
|
@@ -150,9 +165,15 @@ files:
|
|
150
165
|
- images/print_mutations.png
|
151
166
|
- images/print_traces.png
|
152
167
|
- lib/tapping_device.rb
|
168
|
+
- lib/tapping_device/configurable.rb
|
153
169
|
- lib/tapping_device/exceptions.rb
|
154
170
|
- lib/tapping_device/manageable.rb
|
155
|
-
- 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
|
156
177
|
- lib/tapping_device/payload.rb
|
157
178
|
- lib/tapping_device/trackable.rb
|
158
179
|
- lib/tapping_device/trackers/association_call_tracker.rb
|
@@ -1,173 +0,0 @@
|
|
1
|
-
class TappingDevice
|
2
|
-
class OutputPayload < Payload
|
3
|
-
UNDEFINED = "[undefined]"
|
4
|
-
|
5
|
-
alias :raw_arguments :arguments
|
6
|
-
alias :raw_return_value :return_value
|
7
|
-
|
8
|
-
def method_name(options = {})
|
9
|
-
":#{super(options)}"
|
10
|
-
end
|
11
|
-
|
12
|
-
def arguments(options = {})
|
13
|
-
generate_string_result(raw_arguments, options[:inspect])
|
14
|
-
end
|
15
|
-
|
16
|
-
def return_value(options = {})
|
17
|
-
generate_string_result(raw_return_value, options[:inspect])
|
18
|
-
end
|
19
|
-
|
20
|
-
COLOR_CODES = {
|
21
|
-
green: 10,
|
22
|
-
yellow: 11,
|
23
|
-
blue: 12,
|
24
|
-
megenta: 13,
|
25
|
-
cyan: 14,
|
26
|
-
orange: 214
|
27
|
-
}
|
28
|
-
|
29
|
-
COLORS = COLOR_CODES.each_with_object({}) do |(name, code), hash|
|
30
|
-
hash[name] = "\u001b[38;5;#{code}m"
|
31
|
-
end.merge(
|
32
|
-
reset: "\u001b[0m",
|
33
|
-
nocolor: ""
|
34
|
-
)
|
35
|
-
|
36
|
-
PAYLOAD_ATTRIBUTES = {
|
37
|
-
method_name: {symbol: "", color: COLORS[:blue]},
|
38
|
-
location: {symbol: "from:", color: COLORS[:green]},
|
39
|
-
return_value: {symbol: "=>", color: COLORS[:megenta]},
|
40
|
-
arguments: {symbol: "<=", color: COLORS[:orange]},
|
41
|
-
ivar_changes: {symbol: "changes:\n", color: COLORS[:blue]},
|
42
|
-
defined_class: {symbol: "#", color: COLORS[:yellow]}
|
43
|
-
}
|
44
|
-
|
45
|
-
PAYLOAD_ATTRIBUTES.each do |attribute, attribute_options|
|
46
|
-
color = attribute_options[:color]
|
47
|
-
|
48
|
-
alias_method "original_#{attribute}".to_sym, attribute
|
49
|
-
|
50
|
-
# regenerate attributes with `colorize: true` support
|
51
|
-
define_method attribute do |options = {}|
|
52
|
-
call_result = send("original_#{attribute}", options)
|
53
|
-
|
54
|
-
if options[:colorize]
|
55
|
-
"#{color}#{call_result}#{COLORS[:reset]}"
|
56
|
-
else
|
57
|
-
call_result
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
define_method "#{attribute}_with_color" do |options = {}|
|
62
|
-
send(attribute, options.merge(colorize: true))
|
63
|
-
end
|
64
|
-
|
65
|
-
PAYLOAD_ATTRIBUTES.each do |and_attribute, and_attribute_options|
|
66
|
-
next if and_attribute == attribute
|
67
|
-
|
68
|
-
define_method "#{attribute}_and_#{and_attribute}" do |options = {}|
|
69
|
-
"#{send(attribute, options)} #{and_attribute_options[:symbol]} #{send(and_attribute, options)}"
|
70
|
-
end
|
71
|
-
|
72
|
-
define_method "#{attribute}_and_#{and_attribute}_with_color" do |options = {}|
|
73
|
-
send("#{attribute}_and_#{and_attribute}", options.merge(colorize: true))
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def passed_at(options = {})
|
79
|
-
with_method_head = options.fetch(:with_method_head, false)
|
80
|
-
arg_name = raw_arguments.keys.detect { |k| raw_arguments[k] == target }
|
81
|
-
|
82
|
-
return unless arg_name
|
83
|
-
|
84
|
-
arg_name = ":#{arg_name}"
|
85
|
-
arg_name = value_with_color(arg_name, :orange) if options[:colorize]
|
86
|
-
msg = "Passed as #{arg_name} in '#{defined_class(options)}##{method_name(options)}' at #{location(options)}"
|
87
|
-
msg += "\n > #{method_head.strip}" if with_method_head
|
88
|
-
msg
|
89
|
-
end
|
90
|
-
|
91
|
-
def detail_call_info(options = {})
|
92
|
-
<<~MSG
|
93
|
-
#{method_name_and_defined_class(options)}
|
94
|
-
from: #{location(options)}
|
95
|
-
<= #{arguments(options)}
|
96
|
-
=> #{return_value(options)}
|
97
|
-
|
98
|
-
MSG
|
99
|
-
end
|
100
|
-
|
101
|
-
def ivar_changes(options = {})
|
102
|
-
super.map do |ivar, value_changes|
|
103
|
-
before = generate_string_result(value_changes[:before], options[:inspect])
|
104
|
-
after = generate_string_result(value_changes[:after], options[:inspect])
|
105
|
-
|
106
|
-
if options[:colorize]
|
107
|
-
ivar = "#{COLORS[:orange]}#{ivar}#{COLORS[:reset]}"
|
108
|
-
before = "#{COLORS[:blue]}#{before.to_s}#{COLORS[:reset]}"
|
109
|
-
after = "#{COLORS[:blue]}#{after.to_s}#{COLORS[:reset]}"
|
110
|
-
end
|
111
|
-
|
112
|
-
" #{ivar}: #{before.to_s} => #{after.to_s}"
|
113
|
-
end.join("\n")
|
114
|
-
end
|
115
|
-
|
116
|
-
def call_info_with_ivar_changes(options = {})
|
117
|
-
<<~MSG
|
118
|
-
#{method_name_and_defined_class(options)}
|
119
|
-
from: #{location(options)}
|
120
|
-
changes:
|
121
|
-
#{ivar_changes(options)}
|
122
|
-
|
123
|
-
MSG
|
124
|
-
end
|
125
|
-
|
126
|
-
private
|
127
|
-
|
128
|
-
def value_with_color(value, color)
|
129
|
-
"#{COLORS[color]}#{value}#{COLORS[:reset]}"
|
130
|
-
end
|
131
|
-
|
132
|
-
def generate_string_result(obj, inspect)
|
133
|
-
case obj
|
134
|
-
when Array
|
135
|
-
array_to_string(obj, inspect)
|
136
|
-
when Hash
|
137
|
-
hash_to_string(obj, inspect)
|
138
|
-
when UNDEFINED
|
139
|
-
UNDEFINED
|
140
|
-
when String
|
141
|
-
"\"#{obj}\""
|
142
|
-
when nil
|
143
|
-
"nil"
|
144
|
-
else
|
145
|
-
inspect ? obj.inspect : obj.to_s
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
def array_to_string(array, inspect)
|
150
|
-
elements_string = array.map do |elem|
|
151
|
-
generate_string_result(elem, inspect)
|
152
|
-
end.join(", ")
|
153
|
-
"[#{elements_string}]"
|
154
|
-
end
|
155
|
-
|
156
|
-
def hash_to_string(hash, inspect)
|
157
|
-
elements_string = hash.map do |key, value|
|
158
|
-
"#{key.to_s}: #{generate_string_result(value, inspect)}"
|
159
|
-
end.join(", ")
|
160
|
-
"{#{elements_string}}"
|
161
|
-
end
|
162
|
-
|
163
|
-
def obj_to_string(element, inspect)
|
164
|
-
to_string_method = inspect ? :inspect : :to_s
|
165
|
-
|
166
|
-
if !inspect && element.is_a?(String)
|
167
|
-
"\"#{element}\""
|
168
|
-
else
|
169
|
-
element.send(to_string_method)
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
173
|
-
end
|