Testing a REPL in Ruby with RSpec and threads - ruby

I'm using RSpec to test the behavior of a simple REPL. The REPL just echoes back whatever the input was, unless the input was "exit", in which case it terminates the loop.
To avoid hanging the test runner, I'm running the REPL method inside a separate thread. To make sure that the code in the thread has executed before I write expectations about it, I've found it necessary to include a brief sleep call. If I remove it, the tests fail intermittently because the expectations are sometimes made before the code in the thread has run.
What is a good way to structure the code and spec such that I can make expectations about the REPL's behavior deterministically, without the need for the sleep hack?
Here is the REPL class and the spec:
class REPL
def initialize(stdin = $stdin, stdout = $stdout)
#stdin = stdin
#stdout = stdout
end
def run
#stdout.puts "Type exit to end the session."
loop do
#stdout.print "$ "
input = #stdin.gets.to_s.chomp.strip
break if input == "exit"
#stdout.puts(input)
end
end
end
describe REPL do
let(:stdin) { StringIO.new }
let(:stdout) { StringIO.new }
let!(:thread) { Thread.new { subject.run } }
subject { described_class.new(stdin, stdout) }
# Removing this before hook causes the examples to fail intermittently
before { sleep 0.01 }
after { thread.kill if thread.alive? }
it "prints a message on how to end the session" do
expect(stdout.string).to match(/end the session/)
end
it "prints a prompt for user input" do
expect(stdout.string).to match(/\$ /)
end
it "echoes input" do
stdin.puts("foo")
stdin.rewind
expect(stdout.string).to match(/foo/)
end
end

Instead of letting :stdout be a StringIO, you could back it by a Queue. Then when you try to read from the queue, your tests will just wait until the REPL pushes something into the queue (aka. writes to stdout).
require 'thread'
class QueueIO
def initialize
#queue = Queue.new
end
def write(str)
#queue.push(str)
end
def puts(str)
write(str + "\n")
end
def read
#queue.pop
end
end
let(:stdout) { QueueIO.new }
I just wrote this up without trying it out, and it may not be robust enough for your needs, but it gets the point across. If you use a data structure to synchronize the two threads like this, then you don't need to sleep at all. Since this removes the non-determinism, you shouldn't see the intermittent failures.

I've used a running? guard for situations like this. You probably can't avoid the sleep entirely, but you can avoid unnecessary sleeps.
First, add a running? method to your REPL class.
class REPL
...
def running?
!!#running
end
def run
#running=true
loop do
...
if input == 'exit
#running = false
break
end
...
end
end
end
Then, in your specs, sleep until the REPL is running:
describe REPL do
...
before { sleep 0.01 until REPL.running? }
...
end

Related

How to isolate methods in Ruby - multiprocessing question

I have 3 methods that do something on the file system but each method changes current directory and I can't use Threading for those since it uses same ruby process and I have racing condition. I found Process.spawn (can't use fork on windows) might be solution but I don't know how to spawn 3 process and call method in each of those.
I tried following as a test, but didn't work
#!/usr/bin/env ruby
def something
puts 'printaj'
end
def nextsomething
puts 'vici'
end
pid1 = Process.spawn(something)
pid2 = Process.spawn(nextsomething)
Process.wait pid1
Process.wait pid2
puts 'both finished'
How about threads and mutex?
semaphore = Mutex.new
a = Thread.new {
semaphore.synchronize {
# do something on the file system
}
}
b = Thread.new {
semaphore.synchronize {
# do something on the file system
}
}
With this code you will run only one operation on file system in the same time.
https://ruby-doc.org/core-2.5.0/Mutex.html

Ruby EventMachine, kill running processes?

How can I kill running processes in EventMachine?
Below is an example, I'm starting 10 processes and then I'm trying to erase them all (but it doesn't work). My goal is to not have the "Finished" output.
require "rubygems"
require "eventmachine"
class Event
def start
sleep(5)
puts Time.now.to_s + ": Finished!"
end
end
EventMachine.run do
events = []
10.times {
handle = Event.new
events << handle
EventMachine.defer(proc {
handle.start
})
}
# Terminate all events!
events.each do |handle|
handle = nil
ObjectSpace.garbage_collect
end
end
I'm aware that I could set a variable and check whether it's set when doing the output, but I feel like this isn't the "real" thing, or is this really the only solution there is?
Try EventMachine.stop_event_loop, it will “cause all open connections and accepting servers to be run down and closed”.

If nothing sent to STDOUT in last x minutes, how to raise an error

I wonder if this is possible, because if it is, it would help me implement what I need for a program I am making:
Is there a way to attach some kind of listener to STDOUT from within a Ruby program, so that if nothing is written (via puts) to STDOUT for a certain time interval, an error is raised?
Writing to STDOUT should otherwise work as expected.
Perhaps something like this:
def new_puts(what)
#time_th.kill if(#time_th)
puts what
#time_th = Thread.new() {
sleep(2)
raise "here"
}
#time_th.abort_on_exception = true
end
new_puts("test")
new_puts("test2")
sleep(10)
new_puts("test3") #too late
or with callback methods:
def callback
puts "Timeout!"
end
def new_puts(what)
#time_th.kill if(#time_th)
puts what
#time_th = Thread.new() {
sleep(2)
self.method(:callback).call
}
end
new_puts("test")
new_puts("test2")
sleep(10)
new_puts("test3") #too late

Script stops while waiting for user input from STDIN.gets

I'm trying to do something like this, where I have two loops going in seperate threads. The problem I am having is that in the main thread, when I use gets and the script is waiting for user input, the other thread is stopped to wait as well.
class Server
def initialize
#server = TCPServer.new(8080)
run
end
def run
#thread = Thread.new(#server) { |server|
while true
newsock = server.accept
puts "some stuff after accept!"
next if !newsock
# some other stuff
end
}
end
end
def processCommand
# some user commands here
end
test = Server.new
while true do
processCommand(STDIN.gets)
end
The above is just a sample of what I want to do.
Is there a way to make the main thread block while waiting for user input?
You might want to take a look at using the select method of the IO class. Take a look at
good select example for handling select with asynchronous input. Depending upon what version of ruby you're using you might have issues with STDIN though, I'm pretty sure it always triggers the select in 1.8.6.
I'm not sure if this is what you are looking for, but I was looking for something similar and this example does exactly what I wanted. The thread will continue processing until the user hits enter, and then the thread will be able to handle your user input as desired.
user_input = nil
t1 = Thread.new do
while !user_input
puts "Running"
end
puts "Stopping per user input: #{user_input}"
end
user_input = STDIN.gets
t1.join

testing threaded code in ruby

I'm writing a delayed_job clone for DataMapper. I've got what I think is working and tested code except for the thread in the worker process. I looked to delayed_job for how to test this but there are now tests for that portion of the code. Below is the code I need to test. ideas? (I'm using rspec BTW)
def start
say "*** Starting job worker #{#name}"
t = Thread.new do
loop do
delay = Update.work_off(self) #this method well tested
break if $exit
sleep delay
break if $exit
end
clear_locks
end
trap('TERM') { terminate_with t }
trap('INT') { terminate_with t }
trap('USR1') do
say "Wakeup Signal Caught"
t.run
end
see also this thread
The best approach, I believe, is to stub the Thread.new method, and make sure that any "complicated" stuff is in it's own method which can be tested individually. Thus you would have something like this:
class Foo
def start
Thread.new do
do_something
end
end
def do_something
loop do
foo.bar(bar.foo)
end
end
end
Then you would test like this:
describe Foo
it "starts thread running do_something" do
f = Foo.new
expect(Thread).to receive(:new).and_yield
expect(f).to receive(:do_something)
f.start
end
it "do_something loops with and calls foo.bar with bar.foo" do
f = Foo.new
expect(f).to receive(:loop).and_yield #for multiple yields: receive(:loop).and_yield.and_yield.and_yield...
expect(foo).to receive(:bar).with(bar.foo)
f.do_something
end
end
This way you don't have to hax around so much to get the desired result.
You could start the worker as a subprocess when testing, waiting for it to fully start, and then check the output / send signals to it.
I suspect you can pick up quite a few concrete testing ideas in this area from the Unicorn project.
Its impossible to test threads completely. Best you can do is to use mocks.
(something like)
object.should_recieve(:trap).with('TERM').and yield
object.start
How about just having the thread yield right in your test.
Thread.stub(:new).and_yield
start
# assertions...

Resources