pry-timetravel 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|