|
22 | 22 | DebugSocket.start(path) |
23 | 23 | end |
24 | 24 |
|
| 25 | + def write(str, socket_path = path) |
| 26 | + 20.times { sleep(0.1) unless File.exist?(socket_path) } |
| 27 | + socket = UNIXSocket.new(socket_path) |
| 28 | + |
| 29 | + raise "Socket write timeout=#{socket_path}" unless socket.wait_writable(1) |
| 30 | + |
| 31 | + socket.write(str) |
| 32 | + socket.close_write |
| 33 | + |
| 34 | + raise "Socket read timeout=#{socket_path}" unless socket.wait_readable(1) |
| 35 | + |
| 36 | + socket.read |
| 37 | + end |
| 38 | + |
25 | 39 | after do |
26 | 40 | DebugSocket.stop |
27 | 41 | 10.times { sleep(1) if File.exist?(path) } |
28 | 42 | raise "did not cleanup socket file" if File.exist?(path) |
29 | 43 | end |
30 | 44 |
|
31 | 45 | it "logs and evals input" do |
32 | | - socket.write("2 + 2") |
33 | | - socket.close_write |
34 | | - expect(socket.read).to eq("4\n") |
| 46 | + expect(write("2 + 2")).to eq("4\n") |
35 | 47 | expect(log_buffer.string).to include('debug-socket-command="2 + 2"') |
36 | 48 | end |
37 | 49 |
|
|
55 | 67 | expect { DebugSocket.start(another_path) } |
56 | 68 | .to raise_exception("debug socket thread already running for this process") |
57 | 69 |
|
58 | | - 10.times { sleep(1) unless File.exist?(another_path) } |
59 | | - another_socket = UNIXSocket.new(another_path) |
60 | | - another_socket.write("Thread.list.each(&:wakeup)") |
61 | | - another_socket.close_write |
62 | | - expect(another_socket.read).to match(/Thread/) |
| 70 | + expect(write("Thread.list.each(&:wakeup)", another_path)).to match(/Thread/) |
63 | 71 | Process.wait(child, Process::WNOHANG) |
64 | 72 | else |
65 | | - DebugSocket.start(another_path) |
66 | | - sleep |
67 | | - sleep(1) |
68 | | - DebugSocket.stop |
69 | | - exit!(1) |
| 73 | + begin |
| 74 | + DebugSocket.start(another_path) |
| 75 | + sleep |
| 76 | + sleep(1) |
| 77 | + DebugSocket.stop |
| 78 | + ensure |
| 79 | + exit!(1) |
| 80 | + end |
70 | 81 | end |
71 | 82 | end |
72 | 83 | end |
73 | 84 |
|
74 | 85 | it "catches errors in the debug socket thread" do |
75 | | - socket.write("asdf}(]") |
76 | | - socket.close_write |
77 | | - expect(socket.read).to eq("") |
| 86 | + expect(write("asdf}(]")).to include("SyntaxError") |
| 87 | + expect(write("2")).to eq("2\n") |
78 | 88 |
|
79 | | - another_socket = UNIXSocket.new(path) |
80 | | - another_socket.write("2") |
81 | | - another_socket.close_write |
82 | | - expect(another_socket.read).to eq("2\n") |
83 | | - |
84 | | - expect(log_buffer.string).to include("debug-socket-error=#<SyntaxError: (eval):1: syntax error") |
| 89 | + expect(log_buffer.string).to match(/debug-socket-error=#<SyntaxError:.*eval.*syntax error/) |
85 | 90 | expect(log_buffer.string).to include('debug-socket-command="2"') |
86 | 91 | end |
87 | 92 |
|
88 | | - context 'with proc' do |
89 | | - before do |
| 93 | + it "isolates the eval from the local scope" do |
| 94 | + expect(write("server = 1")).to eq("1\n") |
| 95 | + expect(write("server = 1")).to eq("1\n") |
| 96 | + end |
| 97 | + |
| 98 | + it "retries socket errors 10 times then dies" do |
| 99 | + 20.times { sleep(0.1) unless File.exist?(path) } |
| 100 | + |
| 101 | + slept = false |
| 102 | + allow(DebugSocket).to receive(:sleep).and_wrap_original do |original, delay| |
| 103 | + next if slept |
| 104 | + |
| 105 | + slept = true |
| 106 | + original.call(delay) |
| 107 | + end |
| 108 | + |
| 109 | + socket = UNIXSocket.new(path) |
| 110 | + socket.write("sleep(1)") |
| 111 | + socket.close |
| 112 | + |
| 113 | + 20.times do |
| 114 | + socket = UNIXSocket.new(path) |
| 115 | + socket.close |
| 116 | + end |
| 117 | + |
| 118 | + almost_there(250) do |
| 119 | + (1..20).each { |i| expect(log_buffer.string).to include("errors=#{i}") } |
| 120 | + expect(log_buffer.string).to include("DebugSocket is broken now") |
| 121 | + end |
| 122 | + end |
| 123 | + |
| 124 | + context "with proc" do |
| 125 | + before do |
90 | 126 | DebugSocket.stop |
91 | 127 | end |
92 | 128 |
|
|
95 | 131 | audit_proc = proc { |input| audit_calls << input } |
96 | 132 |
|
97 | 133 | DebugSocket.start(path, &audit_proc) |
98 | | - |
99 | | - socket.write("2 + 2") |
100 | | - socket.close_write |
101 | | - expect(socket.read).to eq("4\n") |
| 134 | + expect(write("2 + 2")).to eq("4\n") |
102 | 135 | expect(audit_calls).to eq(["2 + 2"]) |
103 | 136 | end |
104 | 137 |
|
|
107 | 140 |
|
108 | 141 | DebugSocket.start(path, &audit_proc) |
109 | 142 |
|
110 | | - socket.write("3 + 3") |
111 | | - socket.close_write |
112 | | - expect(socket.read).to eq("6\n") |
| 143 | + expect(write("3 + 3")).to eq("6\n") |
113 | 144 | # No error should be raised to the client, and the command is processed |
114 | 145 | expect(log_buffer.string).to include('debug-socket-error=callback unsuccessful: #<RuntimeError: audit error> for "3 + 3"') |
115 | 146 | end |
|
120 | 151 | it "returns a stacktrace for all threads" do |
121 | 152 | time_pid = "\\d{4}-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ\\ pid=#{Process.pid}" |
122 | 153 | running_thread = %r{#{time_pid}\ thread\.object_id=#{Thread.current.object_id}\ thread\.status=run\n |
123 | | - .*lib/debug_socket\.rb:\d+:in\ `backtrace'\n |
124 | | - .*lib/debug_socket\.rb:\d+:in\ `block\ in\ backtrace'\n |
125 | | - .*lib/debug_socket\.rb:\d+:in\ `map'\n |
126 | | - .*lib/debug_socket\.rb:\d+:in\ `backtrace'\n |
127 | | - .*spec/debug_socket_spec\.rb:\d+:in\ `block.*'}x |
| 154 | + .*lib/debug_socket\.rb:\d+:in\ .*backtrace'\n |
| 155 | + .*lib/debug_socket\.rb:\d+:in\ .*block\ in.*backtrace'\n |
| 156 | + .*lib/debug_socket\.rb:\d+:in\ .*map'\n |
| 157 | + .*lib/debug_socket\.rb:\d+:in\ .*backtrace'\n |
| 158 | + .*spec/debug_socket_spec\.rb:\d+:in\ .*block.*'}x |
128 | 159 | thread = Thread.new { sleep 1 } |
129 | 160 | sleep 0.1 |
130 | 161 | sleeping_thread = %r{#{time_pid}\ thread\.object_id=#{thread.object_id}\ thread\.status=sleep\n |
131 | | - .*spec/debug_socket_spec\.rb:\d+:in\ `sleep'\n |
132 | | - .*spec/debug_socket_spec\.rb:\d+:in\ `block.*'}x |
| 162 | + .*spec/debug_socket_spec\.rb:\d+:in\ .*sleep'\n |
| 163 | + .*spec/debug_socket_spec\.rb:\d+:in\ .*block.*'}x |
133 | 164 | bt = DebugSocket.backtrace |
134 | 165 | expect(bt).to match(running_thread) |
135 | 166 | expect(bt).to match(sleeping_thread) |
136 | 167 | end |
137 | 168 | end |
138 | 169 |
|
139 | | - describe "stress test", slow: true do |
| 170 | + describe "stress test", :slow do |
140 | 171 | it "works with lots of threads, even in jruby" do |
141 | 172 | threads = Array.new(10) do |
142 | 173 | Thread.new { 100.times { Thread.new { sleep(0.001) }.join } } |
|
0 commit comments