pry-timetravel 0.0.3 → 0.0.4
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/.rspec +3 -0
- data/CHANGES.md +4 -0
- data/README.md +10 -2
- data/Rakefile +2 -2
- data/lib/pry-timetravel.rb +154 -54
- data/lib/pry-timetravel/commands.rb +12 -6
- data/pry-timetravel.gemspec +3 -5
- data/spec/commands_spec.rb +120 -5
- data/spec/spec_helper.rb +22 -3
- metadata +33 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3289f1ed4dd5e11a532a3d191c836616c520f5fa
|
4
|
+
data.tar.gz: 4cca9997562fb1a4ec503a758a869918981056df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2cc90461d45f52950855ddd68fea6ce8182bb53a1baae82e64def62866ead46512dbab867b4b46cc5a92e11f20868e26876e97d2f2315cdb3a4754f08aea0284
|
7
|
+
data.tar.gz: 9d76b24d9980686987bf3b85a9d881652bc1eee7ab4ccc6274017bc8bc568a5ff4b2e6dfc9ff0dee54632b5fab99e2b249272a6683b103fadc7d4bb6d9f5ba18
|
data/.rspec
ADDED
data/CHANGES.md
ADDED
data/README.md
CHANGED
@@ -28,6 +28,10 @@ WARNING: Time travel may cause zombies.
|
|
28
28
|
|
29
29
|
## KNOWN ISSUES
|
30
30
|
|
31
|
+
### General
|
32
|
+
|
33
|
+
* If you close a connection, like to a DB, it is closed for all snapshots
|
34
|
+
|
31
35
|
### Redis fork detection
|
32
36
|
|
33
37
|
Redis checks to see if you forked and yells at you about needing to reconnect.
|
@@ -44,8 +48,12 @@ this to bypass safety measures:
|
|
44
48
|
end
|
45
49
|
end
|
46
50
|
|
47
|
-
|
48
|
-
|
51
|
+
## Similar Technology
|
52
|
+
|
53
|
+
* [Time Travel Python Debugger](https://github.com/TomOnTime/timetravelpdb)
|
54
|
+
* [Elm's Time Traveling Debugger](http://debug.elm-lang.org/)
|
55
|
+
* [OCaml Time Travel](http://caml.inria.fr/pub/docs/manual-ocaml-4.00/manual030.html#htoc195)
|
56
|
+
* [Chronon (java - proprietary)](http://chrononsystems.com/)
|
49
57
|
|
50
58
|
## Meta
|
51
59
|
|
data/Rakefile
CHANGED
data/lib/pry-timetravel.rb
CHANGED
@@ -4,6 +4,15 @@ require 'json'
|
|
4
4
|
|
5
5
|
require_relative 'pry-timetravel/commands'
|
6
6
|
|
7
|
+
# = Timetravel!
|
8
|
+
#
|
9
|
+
# This is a pry plugin that keeps a pool of fork()ed process checkpoints, so
|
10
|
+
# that you can jump "back in time" to a previous place in your execution.
|
11
|
+
#
|
12
|
+
# == Forking Model
|
13
|
+
#
|
14
|
+
# When you create a snapshot, fork() is executed. The parent (original) process
|
15
|
+
# is suspended, and the new child process picks up where that left off.
|
7
16
|
class PryTimetravel
|
8
17
|
class << self
|
9
18
|
|
@@ -15,6 +24,7 @@ class PryTimetravel
|
|
15
24
|
end
|
16
25
|
end
|
17
26
|
|
27
|
+
# TODO: Should probably delay calling this until first snapshot
|
18
28
|
at_exit do
|
19
29
|
PryTimetravel.dlog "at_exit"
|
20
30
|
if $root_parent && $$ != $root_parent
|
@@ -24,136 +34,200 @@ class PryTimetravel
|
|
24
34
|
end
|
25
35
|
|
26
36
|
def enter_suspended_animation
|
27
|
-
dlog("Installing SIGCONT trap")
|
37
|
+
dlog("Suspend: Installing SIGCONT trap")
|
28
38
|
old_sigcont_handler = Signal.trap('CONT') do
|
29
39
|
dlog("Got a SIGCONT")
|
30
40
|
end
|
31
41
|
|
32
|
-
dlog("Installing SIGEXIT trap")
|
42
|
+
dlog("Suspend: Installing SIGEXIT trap")
|
33
43
|
old_sigexit_handler = Signal.trap('EXIT') do
|
34
44
|
dlog("got EXIT")
|
35
45
|
Kernel.exit! true
|
36
46
|
end
|
37
47
|
|
38
|
-
dlog("Stopping myself")
|
48
|
+
dlog("Suspend: Stopping myself")
|
39
49
|
Process.kill 'SIGSTOP', $$
|
40
|
-
dlog("Back from SIGSTOP!")
|
41
50
|
|
42
|
-
|
51
|
+
dlog("Resume: Back from SIGSTOP! Loading snapshot tree")
|
52
|
+
load_global_timetravel_state
|
43
53
|
|
44
|
-
dlog("Returning to old SIGCONT")
|
54
|
+
dlog("Resume: Returning to old SIGCONT")
|
45
55
|
Signal.trap('CONT', old_sigcont_handler || "DEFAULT")
|
46
56
|
|
47
|
-
dlog("Returning to old SIGEXIT")
|
57
|
+
dlog("Resume: Returning to old SIGEXIT")
|
48
58
|
Signal.trap('EXIT', old_sigexit_handler || "DEFAULT")
|
49
59
|
end
|
50
60
|
|
61
|
+
# The root parent is a sort of overseer of the whole tree, and something
|
62
|
+
# that is still alive and waiting for any signals from the outside world.
|
63
|
+
#
|
64
|
+
# Once any time travel starts, the parent forks and waits for its one and only direct child. All the other action happens in that child and its own children.
|
65
|
+
#
|
66
|
+
# If you USR1 this root parent, it will try to clean up the entire tree.
|
67
|
+
#
|
68
|
+
# It ignores INT, so that if you ^C the process it won't die so quickly.
|
51
69
|
def start_root_parent
|
52
70
|
$root_parent = $$
|
53
71
|
child_pid = fork
|
54
72
|
if child_pid
|
55
73
|
Signal.trap('INT') do
|
56
|
-
dlog("
|
74
|
+
dlog("Root-parent: Got INT, ignoring")
|
57
75
|
end
|
58
76
|
Signal.trap('USR1') do
|
59
|
-
dlog("
|
60
|
-
|
77
|
+
dlog("Root-parent: Got USR1, exiting")
|
78
|
+
cleanup_global_timetravel_state
|
61
79
|
Kernel.exit! true
|
62
80
|
end
|
63
|
-
dlog "Root parent
|
64
|
-
# sleep
|
65
|
-
# Process.waitall
|
81
|
+
dlog "Root parent: Waiting on child pid #{child_pid}"
|
66
82
|
Process.waitpid child_pid
|
67
|
-
|
68
|
-
|
83
|
+
dlog "Root parent: Exiting after wait"
|
84
|
+
cleanup_global_timetravel_state
|
69
85
|
FileUtils.rm("/tmp/timetravel_#{$root_parent}.json")
|
70
86
|
Kernel.exit! true
|
71
87
|
end
|
72
88
|
end
|
73
89
|
|
74
|
-
def
|
90
|
+
def auto_snapshot(target)
|
91
|
+
if !@do_trace
|
92
|
+
@trace = TracePoint.new(:line) do |tp|
|
93
|
+
# puts "trace status: #{$in_trace}"
|
94
|
+
if !$in_trace
|
95
|
+
$in_trace = true
|
96
|
+
# puts "path: #{File.expand_path(tp.path)}"
|
97
|
+
# puts "snapshotting trace"
|
98
|
+
# if ! (tp.defined_class.to_s =~ /Pry/)
|
99
|
+
# if tp.path =~ /<main>/
|
100
|
+
# if tp.path =~ /<main>/
|
101
|
+
# p [tp.path, tp.lineno, tp.event, tp.defined_class, Dir.pwd] # , tp.raised_exception]
|
102
|
+
# end
|
103
|
+
# if tp.path.include?(Dir.pwd) || tp.path =~ /<main>/
|
104
|
+
if File.expand_path(tp.path).include?(Dir.pwd)
|
105
|
+
p [tp.path, tp.lineno, tp.event, tp.defined_class, Dir.pwd] # , tp.raised_exception]
|
106
|
+
# p caller
|
107
|
+
# if tp.path =~ /<main>/
|
108
|
+
# # p tp
|
109
|
+
# p [tp.path, tp.lineno, tp.event] # , tp.raised_exception]
|
110
|
+
PryTimetravel.snapshot(
|
111
|
+
tp.binding,
|
112
|
+
# now_do: -> { run(args.join(" ")) unless args.empty? },
|
113
|
+
# on_return_do: -> { run('whereami') }
|
114
|
+
)
|
115
|
+
# end
|
116
|
+
end
|
117
|
+
$in_trace = false
|
118
|
+
end
|
119
|
+
end
|
120
|
+
@trace.enable
|
121
|
+
@do_trace = true
|
122
|
+
else
|
123
|
+
@trace.disable
|
124
|
+
@do_trace = false
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def update_current_snapshot_info(target, parent_pid = nil)
|
129
|
+
my_pid = $$.to_s
|
130
|
+
@snap_tree ||= {}
|
131
|
+
@snap_tree[my_pid] ||= {}
|
132
|
+
@snap_tree[my_pid]["file"] = target.eval('__FILE__')
|
133
|
+
@snap_tree[my_pid]["line"] = target.eval('__LINE__')
|
134
|
+
@snap_tree[my_pid]["time"] = Time.now.to_f
|
135
|
+
@snap_tree[my_pid]["id"] = @id
|
136
|
+
@snap_tree[my_pid]["previous"] = parent_pid if parent_pid
|
137
|
+
end
|
138
|
+
|
139
|
+
def snapshot(target, opts = {})
|
140
|
+
opts[:now_do] ||= -> {}
|
141
|
+
opts[:on_return_do] ||= -> {}
|
75
142
|
|
76
143
|
# We need a root-parent to keep the shell happy
|
77
144
|
if ! $root_parent
|
78
145
|
start_root_parent
|
79
146
|
end
|
80
147
|
|
148
|
+
@id ||= 0
|
81
149
|
@timetravel_root ||= $$
|
82
|
-
|
83
|
-
|
84
|
-
"file" => target.eval('__FILE__'),
|
85
|
-
"line" => target.eval('__LINE__'),
|
86
|
-
}
|
87
|
-
}
|
150
|
+
update_current_snapshot_info(target)
|
151
|
+
@id += 1
|
88
152
|
|
89
153
|
parent_pid = $$
|
90
154
|
child_pid = fork
|
91
155
|
|
92
156
|
if child_pid
|
93
157
|
|
94
|
-
dlog("I am parent #{parent_pid}
|
158
|
+
dlog("Snapshot: I am parent #{parent_pid}. I have a child pid #{child_pid}. Now suspending.")
|
95
159
|
enter_suspended_animation
|
96
160
|
|
97
|
-
|
98
|
-
|
161
|
+
dlog("Snapshot: Back from suspended animation. Running on_return_do.")
|
162
|
+
# Perform operation now that we've come back
|
163
|
+
opts[:on_return_do].()
|
99
164
|
|
100
165
|
else
|
101
166
|
|
102
167
|
child_pid = $$
|
103
|
-
dlog("I am child #{child_pid}
|
168
|
+
dlog("Snapshot: I am child #{child_pid}. I have a parent pid #{parent_pid}")
|
104
169
|
|
105
|
-
|
106
|
-
"previous" => parent_pid,
|
107
|
-
"file" => target.eval('__FILE__'),
|
108
|
-
"line" => target.eval('__LINE__'),
|
109
|
-
}
|
170
|
+
update_current_snapshot_info(target, parent_pid)
|
110
171
|
|
111
|
-
# Perform
|
112
|
-
|
172
|
+
# Perform immediate operation
|
173
|
+
dlog("Snapshot: Running now_do.")
|
174
|
+
opts[:now_do].()
|
113
175
|
|
114
176
|
end
|
115
177
|
end
|
116
178
|
|
117
|
-
def snapshot_list(target, indent = "", node = @timetravel_root.to_s)
|
118
|
-
|
179
|
+
def snapshot_list(target, indent = "", node = @timetravel_root.to_s, my_indent = nil)
|
180
|
+
if node == ""
|
181
|
+
return "No snapshots"
|
182
|
+
end
|
119
183
|
return unless node && node != ""
|
120
184
|
|
121
|
-
#
|
122
|
-
|
123
|
-
@snap_tree[$$.to_s]["line"] = target.eval('__LINE__')
|
185
|
+
# Freshen the current snapshot so it looks right
|
186
|
+
update_current_snapshot_info(target)
|
124
187
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
188
|
+
code_line = IO.readlines(@snap_tree[node]["file"])[@snap_tree[node]["line"].to_i - 1].chomp
|
189
|
+
|
190
|
+
out = "#{my_indent || indent}#{node} (#{@snap_tree[node]["id"]}) #{@snap_tree[node]["file"]} #{@snap_tree[node]["line"]} #{ node == $$.to_s ? '***' : ''} #{code_line}\n"
|
191
|
+
children = @snap_tree.keys.select { |n| @snap_tree[n]["previous"] == node.to_i }
|
192
|
+
if children.length == 1
|
193
|
+
out += snapshot_list(target, indent, children[0])
|
194
|
+
else
|
195
|
+
children.each { |n|
|
196
|
+
out += snapshot_list(target, indent + " ", n, indent + "> ")
|
197
|
+
}
|
130
198
|
end
|
199
|
+
# @snap_tree.keys.select { |n|
|
200
|
+
# @snap_tree[n]["previous"] == node.to_i
|
201
|
+
# }.each do |n|
|
202
|
+
# out += snapshot_list(target, indent + " ", n)
|
203
|
+
# end
|
131
204
|
out
|
132
205
|
end
|
133
206
|
|
134
|
-
def restore_snapshot(target, target_pid = nil
|
135
|
-
dlog("
|
207
|
+
def restore_snapshot(target, target_pid = nil)
|
208
|
+
dlog("Restore: Trying to restore,");
|
136
209
|
|
137
210
|
if target_pid.nil? && @snap_tree && ! @snap_tree[$$.to_s].nil?
|
138
|
-
count = 1 if count < 1
|
139
211
|
target_pid = @snap_tree[$$.to_s]["previous"]
|
140
|
-
@snap_tree[$$.to_s]["file"] = target.eval('__FILE__')
|
141
|
-
@snap_tree[$$.to_s]["line"] = target.eval('__LINE__')
|
142
212
|
else
|
143
213
|
target_pid = target_pid
|
144
214
|
end
|
145
215
|
|
146
|
-
if target_pid
|
147
|
-
dlog("
|
216
|
+
if target_pid && @snap_tree[target_pid.to_s]
|
217
|
+
dlog("Restore: I found a target pid #{target_pid}! TIME TRAVEL TIME")
|
148
218
|
|
149
|
-
|
150
|
-
|
151
|
-
|
219
|
+
# Update our current information of our current running snapshot
|
220
|
+
update_current_snapshot_info(target)
|
221
|
+
|
222
|
+
save_global_timetravel_state
|
152
223
|
|
224
|
+
# Bring our target back to life
|
153
225
|
Process.kill 'SIGCONT', target_pid
|
226
|
+
|
227
|
+
# Go to sleeeeeeeppppp
|
154
228
|
enter_suspended_animation
|
155
229
|
else
|
156
|
-
dlog("I was unable to time travel. Maybe it is a myth.");
|
230
|
+
dlog("Restore: I was unable to time travel. Maybe it is a myth.");
|
157
231
|
puts "No previous snapshot found."
|
158
232
|
end
|
159
233
|
end
|
@@ -162,6 +236,32 @@ class PryTimetravel
|
|
162
236
|
restore_snapshot(@timetravel_root) if @timetravel_root
|
163
237
|
end
|
164
238
|
|
239
|
+
def global_timetravel_state_filename
|
240
|
+
"/tmp/timetravel_#{$root_parent}.json"
|
241
|
+
end
|
242
|
+
|
243
|
+
def save_global_timetravel_state
|
244
|
+
File.open(global_timetravel_state_filename, 'w') do |f|
|
245
|
+
global_state = {
|
246
|
+
snap_tree: @snap_tree,
|
247
|
+
do_trace: @do_trace
|
248
|
+
}
|
249
|
+
f.puts global_state.to_json
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def load_global_timetravel_state
|
254
|
+
global_state = JSON.parse(File.read(global_timetravel_state_filename))
|
255
|
+
@snap_tree = global_state["snap_tree"]
|
256
|
+
@do_trace = global_state["do_trace"]
|
257
|
+
dlog("Loaded: " + @snap_tree.to_json)
|
258
|
+
@id = (@snap_tree.values.map{|snap| snap['id']}.max || 0) + 1
|
259
|
+
end
|
260
|
+
|
261
|
+
def cleanup_global_timetravel_state
|
262
|
+
FileUtils.rm(global_timetravel_state_filename)
|
263
|
+
end
|
264
|
+
|
165
265
|
end
|
166
266
|
end
|
167
267
|
|
@@ -4,10 +4,11 @@ Pry::Commands.create_command "snap", "Create a snapshot that you can later retur
|
|
4
4
|
group 'Timetravel'
|
5
5
|
banner <<-'BANNER'
|
6
6
|
Usage: snap [cmd]
|
7
|
-
snap
|
7
|
+
snap -l|--list List existing snapshots
|
8
|
+
snap -a|--auto Automatically take new snapshots
|
8
9
|
|
9
10
|
This will add a snapshot which you can return to later.
|
10
|
-
|
11
|
+
|
11
12
|
If you provide [cmd] then that command will also be run -- nice for "snap next" in conjunction with pry-byebug.
|
12
13
|
BANNER
|
13
14
|
|
@@ -17,17 +18,19 @@ Pry::Commands.create_command "snap", "Create a snapshot that you can later retur
|
|
17
18
|
# :optional_argument => true, :as => Integer
|
18
19
|
opt.on :l, :list,
|
19
20
|
"Show a list of existing snapshots"
|
21
|
+
opt.on :a, :auto, "Automatically take snapshots!"
|
20
22
|
end
|
21
23
|
def process
|
22
24
|
if opts.l?
|
23
25
|
output.puts PryTimetravel.snapshot_list(target)
|
26
|
+
elsif opts.a?
|
27
|
+
output.puts PryTimetravel.auto_snapshot(target)
|
24
28
|
else
|
25
29
|
PryTimetravel.snapshot(
|
26
30
|
target,
|
27
|
-
-> { run(args.join(" ")) unless args.empty? },
|
28
|
-
-> { run('whereami') }
|
31
|
+
now_do: -> { run(args.join(" ")) unless args.empty? },
|
32
|
+
on_return_do: -> { run('whereami') }
|
29
33
|
)
|
30
|
-
# run('whereami')
|
31
34
|
end
|
32
35
|
end
|
33
36
|
end
|
@@ -49,7 +52,7 @@ Pry::Commands.create_command "back", "Go back to the most recent snapshot" do
|
|
49
52
|
opt.on :home, "Jump to the end of the original execution sequence"
|
50
53
|
end
|
51
54
|
def process
|
52
|
-
if opts.
|
55
|
+
if opts.home?
|
53
56
|
PryTimetravel.restore_root_snapshot(target)
|
54
57
|
else
|
55
58
|
target_pid = args.first ? args.first.to_i : opts[:p]
|
@@ -58,3 +61,6 @@ Pry::Commands.create_command "back", "Go back to the most recent snapshot" do
|
|
58
61
|
end
|
59
62
|
end
|
60
63
|
|
64
|
+
Pry::Commands.alias_command 'n', 'snap next'
|
65
|
+
Pry::Commands.alias_command 's', 'snap step'
|
66
|
+
Pry::Commands.alias_command 'p', 'back'
|
data/pry-timetravel.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'pry-timetravel'
|
3
|
-
s.version = '0.0.
|
3
|
+
s.version = '0.0.4'
|
4
4
|
s.summary = 'Timetravel'
|
5
5
|
s.description = 'Allows you to timetravel!'
|
6
6
|
s.homepage = 'https://github.com/awwaiid/pry-timetravel'
|
@@ -12,8 +12,6 @@ Gem::Specification.new do |s|
|
|
12
12
|
s.add_dependency 'pry'
|
13
13
|
|
14
14
|
s.add_development_dependency 'rake'
|
15
|
-
|
16
|
-
|
17
|
-
# s.add_development_dependency 'redcarpet'
|
18
|
-
# s.add_development_dependency 'capybara'
|
15
|
+
s.add_development_dependency 'rspec'
|
16
|
+
s.add_development_dependency 'pry-byebug'
|
19
17
|
end
|
data/spec/commands_spec.rb
CHANGED
@@ -1,11 +1,126 @@
|
|
1
1
|
require './spec/spec_helper'
|
2
|
+
require 'pty'
|
3
|
+
require 'expect'
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
|
5
|
+
pry_timetravel_cmd = "TERM=dumb pry -f --no-color --no-pager --no-history --noprompt -s timetravel"
|
6
|
+
|
7
|
+
describe "pry-timetravel" do
|
8
|
+
it "starts with no snapshots" do
|
9
|
+
PTY.spawn(pry_timetravel_cmd) do |reader, writer|
|
10
|
+
writer.puts("snap --list")
|
11
|
+
found = reader.expect(/No snapshots/,1)
|
12
|
+
expect(found).to be_truthy
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it "Exits with 0 snapshots cleanly" do
|
17
|
+
saved_pid = nil
|
18
|
+
PTY.spawn(pry_timetravel_cmd) do |reader, writer, cmd_pid|
|
19
|
+
saved_pid = cmd_pid
|
20
|
+
writer.puts("snap --list")
|
21
|
+
found = reader.expect(/No snapshots/,1)
|
22
|
+
expect(found).to be_truthy
|
23
|
+
writer.puts("exit")
|
24
|
+
sleep 0.1 # Give time to exit?
|
25
|
+
end
|
26
|
+
|
27
|
+
pid_list = `ps h -o pid,ppid -s #{saved_pid}`.split(/\n/)
|
28
|
+
expect(pid_list.count).to be == 0
|
29
|
+
end
|
30
|
+
|
31
|
+
it "creates one snapshot" do
|
32
|
+
PTY.spawn(pry_timetravel_cmd) do |reader, writer, cmd_pid|
|
33
|
+
writer.puts("snap")
|
34
|
+
writer.puts("snap --list")
|
35
|
+
|
36
|
+
all1, pid1 = reader.expect(/^(\d+) \(0\) <main> 1/,1)
|
37
|
+
all2, pid2 = reader.expect(/^ (\d+) \(1\) <main> 1 \*\*\*/,1)
|
38
|
+
|
39
|
+
expect(pid1.to_i).to be > 0
|
40
|
+
expect(pid2.to_i).to be > 0
|
41
|
+
|
42
|
+
pid_list = `ps h -o pid,ppid,sess -s #{cmd_pid}`.split(/\n/)
|
43
|
+
# 0: shell
|
44
|
+
# 1: root
|
45
|
+
# 2: base snapshot
|
46
|
+
# 3: current running branch
|
47
|
+
expect(pid_list.count).to be == 4
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it "creates second snapshot" do
|
52
|
+
PTY.spawn(pry_timetravel_cmd) do |reader, writer, cmd_pid|
|
53
|
+
writer.puts("snap")
|
54
|
+
writer.puts("snap")
|
55
|
+
writer.puts("snap --list")
|
56
|
+
|
57
|
+
all1, pid1 = reader.expect(/^(\d+) \(0\) <main> 1/,1)
|
58
|
+
all2, pid2 = reader.expect(/^ (\d+) \(1\) <main> 1/,1)
|
59
|
+
all3, pid3 = reader.expect(/^ (\d+) \(2\) <main> 1 \*\*\*/,1)
|
60
|
+
|
61
|
+
expect(pid1.to_i).to be > 0
|
62
|
+
expect(pid2.to_i).to be > 0
|
63
|
+
expect(pid3.to_i).to be > 0
|
64
|
+
|
65
|
+
pid_list = `ps h -o pid,ppid -s #{cmd_pid}`.split(/\n/)
|
66
|
+
# 0: shell
|
67
|
+
# 1: root
|
68
|
+
# 2: base snapshot
|
69
|
+
# 2: second snapshot
|
70
|
+
# 3: current running branch
|
71
|
+
expect(pid_list.count).to be == 5
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it "Can time-travel to before a var existed" do
|
76
|
+
PTY.spawn(pry_timetravel_cmd) do |reader, writer, cmd_pid|
|
77
|
+
writer.puts("snap")
|
78
|
+
writer.puts("x = 7")
|
79
|
+
reader.expect(/^=> 7/,1)
|
80
|
+
writer.puts("x")
|
81
|
+
reader.expect(/^=> 7/,1)
|
82
|
+
writer.puts("back")
|
83
|
+
reader.expect(/^At the top level\./,1)
|
84
|
+
writer.puts("x")
|
85
|
+
result = reader.expect(/^NameError: undefined local variable or method `x' for main:Object/,1)
|
86
|
+
expect(result).to be_truthy
|
87
|
+
end
|
6
88
|
end
|
7
|
-
|
8
|
-
|
89
|
+
|
90
|
+
it "Can time-travel to a previous var value" do
|
91
|
+
PTY.spawn(pry_timetravel_cmd) do |reader, writer, cmd_pid|
|
92
|
+
writer.puts("x = 7")
|
93
|
+
expect(reader.expect(/^=> 7/,1)).to be_truthy
|
94
|
+
writer.puts("snap")
|
95
|
+
writer.puts("x")
|
96
|
+
expect(reader.expect(/^=> 7/,1)).to be_truthy
|
97
|
+
writer.puts("snap")
|
98
|
+
writer.puts("x = 7")
|
99
|
+
reader.expect(/^=> 13/,1)
|
100
|
+
writer.puts("back")
|
101
|
+
reader.expect(/^At the top level\./,1)
|
102
|
+
writer.puts("x")
|
103
|
+
result = reader.expect(/^=> 7/,1)
|
104
|
+
expect(result).to be_truthy
|
105
|
+
end
|
9
106
|
end
|
107
|
+
|
108
|
+
it "Can auto-checkpoint" do
|
109
|
+
PTY.spawn(pry_timetravel_cmd) do |reader, writer, cmd_pid|
|
110
|
+
writer.puts("snap -a")
|
111
|
+
writer.puts("x = 7")
|
112
|
+
expect(reader.expect(/^=> 7/,1)).to be_truthy
|
113
|
+
writer.puts("x")
|
114
|
+
expect(reader.expect(/^=> 7/,1)).to be_truthy
|
115
|
+
writer.puts("x = 13")
|
116
|
+
expect(reader.expect(/^=> 13/,1)).to be_truthy
|
117
|
+
writer.puts("back")
|
118
|
+
expect(reader.expect(/^At the top level\./,1)).to be_truthy
|
119
|
+
writer.puts("x")
|
120
|
+
result = reader.expect(/^=> 7/,1)
|
121
|
+
expect(result).to be_truthy
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
10
125
|
end
|
11
126
|
|
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,25 @@
|
|
1
1
|
require 'rspec'
|
2
|
-
require '
|
2
|
+
require './lib/pry-timetravel'
|
3
3
|
|
4
|
-
require 'pry/test/helper'
|
4
|
+
#require 'pry/test/helper'
|
5
5
|
|
6
|
-
|
6
|
+
# in case the tests call reset_defaults, ensure we reset them to
|
7
|
+
# amended (test friendly) values
|
8
|
+
# class << Pry
|
9
|
+
# alias_method :orig_reset_defaults, :reset_defaults
|
10
|
+
# def reset_defaults
|
11
|
+
# orig_reset_defaults
|
12
|
+
|
13
|
+
# Pry.config.color = false
|
14
|
+
# Pry.config.pager = false
|
15
|
+
# Pry.config.should_load_rc = false
|
16
|
+
# Pry.config.should_load_local_rc= false
|
17
|
+
# Pry.config.should_load_plugins = false
|
18
|
+
# Pry.config.history.should_load = false
|
19
|
+
# Pry.config.history.should_save = false
|
20
|
+
# Pry.config.correct_indent = false
|
21
|
+
# Pry.config.hooks = Pry::Hooks.new
|
22
|
+
# Pry.config.collision_warning = false
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
# Pry.reset_defaults
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pry-timetravel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brock Wilcox
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-01-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pry
|
@@ -38,6 +38,34 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry-byebug
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
41
69
|
description: Allows you to timetravel!
|
42
70
|
email:
|
43
71
|
- awwaiid@thelackthereof.org
|
@@ -46,7 +74,9 @@ extensions: []
|
|
46
74
|
extra_rdoc_files: []
|
47
75
|
files:
|
48
76
|
- ".gitignore"
|
77
|
+
- ".rspec"
|
49
78
|
- ".travis.yml"
|
79
|
+
- CHANGES.md
|
50
80
|
- Gemfile
|
51
81
|
- LICENSE.MIT
|
52
82
|
- README.md
|
@@ -76,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
106
|
version: '0'
|
77
107
|
requirements: []
|
78
108
|
rubyforge_project:
|
79
|
-
rubygems_version: 2.
|
109
|
+
rubygems_version: 2.5.1
|
80
110
|
signing_key:
|
81
111
|
specification_version: 4
|
82
112
|
summary: Timetravel
|