tapping_device 0.5.2 → 0.5.3
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 +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
|