In RSpec 2, how can I spawn off a process, run some examples and then kill that process? - ruby

I am trying to run some functional test on a small server I have created. I am running Ruby 1.9.2 and RSpec 2.2.1 on Mac OS X 10.6. I have verified that the server works correctly and is not causing the problems I am experiencing. In my spec, I am attempting to spawn of a process to start the server, run some examples, and then kill the process running the server. Here is the code for my spec:
describe "Server" do
describe "methods" do
let(:put) { "put foobar beans 5\nhowdy" }
before(:all) do
#pid = spawn("bin/server")
end
before(:each) do
#sock = TCPSocket.new "127.0.0.1", 3000
end
after(:each) do
#sock.close
end
after(:all) do
Process.kill("HUP", #pid)
end
it "should be valid for a valid put method" do
#sock.send(put, 0).should == put.length
response = #sock.recv(1000)
response.should == "OK\n"
end
#more examples . . .
end
end
When I run the spec, it appears that the before(:all) and after(:all) blocks are run and the server processes is killed before the examples are run, because I get the following output:
F
Failures:
1) Server methods should be valid for a valid put method
Failure/Error: #sock = TCPSocket.new "127.0.0.1", 3000
Connection refused - connect(2)
# ./spec/server_spec.rb:11:in `initialize'
# ./spec/server_spec.rb:11:in `new'
# ./spec/server_spec.rb:11:in `block (3 levels) in <top (required)>'
When I comment out the call to Process.kill, the server is started and the tests are run, but the server remains running, which means I have to go manually kill it.
It seems like I am misunderstanding what the after(:all) method is supposed to do, because it is not being run in the order I thought it would. Why is this happening? What do I need to do so that my specs

Are you sure the server is ready to accept connections? Maybe something like this would help:
before(:each) do
3.times do
begin
#sock = TCPSocket.new "127.0.0.1", 2000
break
rescue
sleep 1
end
end
raise "could not open connection" unless #sock
end

Related

Threaded output not displayed when run from command (works from irb)

I decided to rewrite a ruby script I did as a gem/module with an executable component. The original works fine, but as a gem it is easier to maintain/install with all dependencies.
The command takes an array of hosts and a command, and runs it on the hosts via threads. The problem I am now having though is that the output of the command is not displayed on the terminal. Executing the module code from within IRB however produces the output.
The module code is as follows:
require "rcmd/version"
require 'net/ssh'
require 'thread'
module Rcmd
#queue = Queue.new
class << self
attr_accessor :nthreads
attr_accessor :user
attr_accessor :quiet
attr_accessor :command
attr_accessor :host_list
attr_accessor :threads
end
# Built in function called by each thread for executing the command on individual hosts
def Rcmd.run_command_on_host(conn_options)
begin
# Create ssh session to host
Net::SSH.start(conn_options[:host], conn_options[:user], :password => conn_options[:passwd]) do |session|
# Open channel for input/output control
session.open_channel do |channel|
channel.on_data do |ch, data|
# Print recieved data if quiet is not true
puts "#{conn_options[:host]} :: #{data}" unless conn_options[:quiet]
end
channel.on_extended_data do |ch,type,data|
# Always print stderr data
puts "#{conn_options[:host]} :: ERROR :: #{data}"
end
# Execute command
channel.exec #command
end
# Loop until command completes
session.loop
end
rescue
puts "#{conn_options[:host]} :: CONNECT ERROR :: Unable to connect to host!\n"
end
end
# Main method of module for starting the execution of the specified command on provided hosts
def Rcmd.run_command()
if not #command
raise ArgumentError.new("No command set for execution")
end
if not #host_list.count >= 1
raise ArgumentError.new("host_list must contain at least one system")
end
#host_list.each do |host|
#queue << host
end
until #queue.empty?
# Don't start more threads then hosts.
num_threads = #nthreads <= #host_list.count ? #nthreads : #host_list.count
# Prepare threads
#threads = { }
num_threads.times do |i|
#threads[i] = Thread.new {
conn_options = { :user => #user, :host => #queue.pop, :password => nil, :quiet => #quiet}
unless conn_options[:host].nil?
self.run_command_on_host(conn_options)
end
}
end
# Execute threads
#threads.each(&:join)
end
end
end
Test snippet:
require 'rcmd'
Rcmd.host_list= ["localhost", "dummy-host"]
Rcmd.nthreads= 2
Rcmd.user= 'root'
Rcmd.command= "hostname -f"
Rcmd.run_command
Running the above in IRB produces:
dummy-host :: CONNECT ERROR :: Unable to connect to host!
localhost :: darkstar.lan
As expected. However, Running the same from a script file (the gem command) or in ruby directly results in no output:
ruby-newb#darkstar:~/repos/rcmd$ rcmd -n localhost,dummy-host -c 'hostname -f'
ruby-newb#darkstar:~/repos/rcmd$
Previously I used $stdout.puts and $stderr.puts as well as the constants variants but in desperation moved it to just puts. I have also tried with print and various other ways to include handing the streams to the threads and printing all output once the thread finished , but to no avail.
After adding tons of 'p' statements through out the code, when running from the command the output stops just before the Net::SSH.start call in the run_command_on_host method.
I have also tried having that entire method within the thread creations but then it failed to execute completely. Hence having two methods. One for creating the threads, and one used by the threads for executing the ssh session and command.
Failing in both ruby-2.3.3 and ruby-2.0.0-p648 so I think I am just doing something stupid.
If someone could find it in their hearts to tell this Ruby newbie where he has gone wrong it would be very appreciated.
I am not sure exactly why but it seems the method call was exiting (not waiting on the threads despite the thread joins) and thus killing the threads. As such, the Net::SSH module did not have time to get/return data.
I added the following unless statement near the end of the code to force the method to wait for the threads to complete and now output is displayed as desired. Also reduced the amount of code a bit.
unless #threads.each.map {|t| t.alive?}.none?
sleep 1
end

Ruby gem 'tcp_timeout' failed to suppress raising error

I wrote a script using 'socket' that connects to a host and port and because socket.timeout doesn't really work I tried using the 'tcp_timeout' gem that works properly but I can't seem to suppress the error raised when connect/read/write timeout happens. Any idea where am I wrong?
begin
socket = TCPTimeout::TCPSocket.new(server, port, connect_timeout: 6, read_timeout: 6)
unless socket.read(12) =~ /^SMTH\n$/
puts "[!] #{server} banner error"
exit(1)
end
rescue TCPTimeout::SocketTimeout => err
puts "[!] #{server} Timeout"
exit(1)
end
The error raised, as expected is a read timeout error:
/usr/local/rvm/gems/ruby-1.9.3-p551/gems/tcp_timeout-0.1.1/lib/tcp_timeout.rb:160:in `select_timeout': read timeout (TCPTimeout::SocketTimeout)
from /usr/local/rvm/gems/ruby-1.9.3-p551/gems/tcp_timeout-0.1.1/lib/tcp_timeout.rb:108:in `block in read'
from /usr/local/rvm/gems/ruby-1.9.3-p551/gems/tcp_timeout-0.1.1/lib/tcp_timeout.rb:107:in `loop'
from /usr/local/rvm/gems/ruby-1.9.3-p551/gems/tcp_timeout-0.1.1/lib/tcp_timeout.rb:107:in `read'
from ./myhost.rb:67:in `<main>'
I tried even:
rescue TCPTimeout::SocketTimeout, StandardError, Timeout::Error => err
Same thing happens.
Author of tcp_timeout here; your code looks correct. This snippet works as expected (for me):
require 'tcp_timeout'
begin
socket = TCPTimeout::TCPSocket.new('stackoverflow.com', 80, read_timeout: 1)
socket.read(100)
rescue TCPTimeout::SocketTimeout => e
puts 'Rescued!', e
end
If you can find a snippet that fails reliably against a public server please file a bug: https://github.com/lann/tcp-timeout-ruby/issues

Right way of stopping a Ruby service

I am using AirBnb Nerve service. It's service code looks like this:
require 'logger'
require 'json'
require 'timeout'
require 'nerve/version'
require 'nerve/utils'
require 'nerve/log'
require 'nerve/ring_buffer'
require 'nerve/reporter'
require 'nerve/service_watcher'
module Nerve
class Nerve
include Logging
def initialize(opts={})
log.info 'nerve: starting up!'
# set global variable for exit signal
$EXIT = false
...some code...
# Any exceptions in the watcher threads should wake the main thread so
# that we can fail fast.
Thread.abort_on_exception = true
log.debug 'nerve: completed init'
end
def run
log.info 'nerve: starting run'
#services.each do |name, config|
launch_watcher(name, config)
end
begin
sleep
rescue StandardError => e
log.error "nerve: encountered unexpected exception #{e.inspect} in main thread"
raise e
ensure
$EXIT = true
log.warn 'nerve: reaping all watchers'
#watchers.each do |name, watcher_thread|
reap_watcher(name)
end
end
log.info 'nerve: exiting'
ensure
$EXIT = true
end
def launch_watcher(name, config)
... some code ...
end
def reap_watcher(name)
... some code ...
end
end
end
I do not see any stop method. What is the right way of stopping such a service? I am using JRuby and intend to write a JSVC adapter for this service.
There is no way to do this via the current API, short of sending it a signal.
If sending a signal isn't going to work and you want to handle stop explicitly, it looks like you will need to change the following things:
Add a #stop method to Nerve that sets $EXIT = true.
Modify #run so that rather than sleeping forever (sleep) it wakes up and checks $EXIT.

EventMachine Rspec connection and send_data test not working

I'm attempting to update the em-irc library to make it work with current versions of Ruby, as well as update it with some new features. I'm trying to make the spec work to my changes, but it's not working as I expect.
One of the tests that's not working, regardless of the changes I introduce, is the send_data context.
subject do
EventMachine::IRC::Client.new
end
...
context 'send_data' do
let(:connection) { mock('Connection') }
before do
subject.stub(:conn => connection)
subject.stub(:connected => true)
end
it 'should return false if not connected' do
subject.stub(:connected => false)
subject.send_data("NICK jch").should == false
end
it 'should send message to irc server' do
connection.should_receive(:send_data).with("NICK jch\r\n")
subject.send_data("NICK jch")
end
end
Which references this function in my code:
def send_data(message)
return false unless #connected
message = message + "\r\n"
#conn.send_data(message)
trigger 'send', message
end
The first test works; when subject is not connected, send_data returns false. However, the second test fails because mock('Connection') never receives the send_data calls. This is the failure I receive:
1) EventMachine::IRC::Client send_data should send message to irc server
Failure/Error: connection.should_receive(:send_data).with("NICK jch\r\n")
(Mock "Connection").send_data("NICK jch\r\n")
expected: 1 time with arguments: ("NICK jch\r\n")
received: 0 times with arguments: ("NICK jch\r\n")
# ./spec/lib/em-irc/client_spec.rb:80:in `block (3 levels) in <top (required)>'
I've tried a couple changes but none of them seem to be working. I don't see why connection isn't receiving send_data calls even though I'm calling send_data on that mocked connection. It was working in the previous version of the library, with the only difference being I use let(:connection){...} rather than #connection = mock('Connection').
in rspec, you need to have tests run inside of an event loop. I achieve that with this monkey patch:
RSpec::Core::Example.class_eval do
alias ignorant_run run
def run(example_group_instance, reporter)
result = false
Fiber.new do
EM.run do
df = EM::DefaultDeferrable.new
df.callback do |test_result|
result = test_result
# stop if we are still running.
# We won't be running if something inside the test
# stops the run loop.
EM.stop if EM.reactor_running?
end
test_result = ignorant_run example_group_instance, reporter
df.set_deferred_status :succeeded, test_result
end
end.resume
result
end
end

eventmachine server failing to execute receive_data

I have an eventmachine app where one script is reading from a file, sending data to another script line by line, and the "server" script is acting upon that data. Unfortunately, the "server" script fails to execute receive_data as it should. I know that a connection is being made because it eecutes post_init, and I know the sender script is sending data. Here is some of my code along with how I start the server.
module BT_Server
def post_init
puts "-- someone connected to the echo server!"
end
def receive_data(data)
puts "hi"
int, time, *int_macs = data.split("-")
# more stuff that isn't needed here
end
def bt_left(dev)
dev.save
if t = Device.macs.index(dev.mac)
Device.all[t].add_int(dev.int, dev.t_0, dev.t_l)
else
Device.new(dev.mac, dev.int, dev.t_0, dev.t_l)
end
return false
end
def unbind
puts "disconnection"
end
end
EventMachine::run {
EventMachine::start_server 'localhost', 8081, BT_Server
puts t_0 = Time.new
puts 'listening...'
}
Note: I have the Module definition in a separate file, along with my classes, which I require into the server script, if that makes any difference.
i tested your code and it outputs 'hi' every time i send something via telnet.
from my point of view, the code is correct.
are you sure the sending script is working? try with a manual telnet on port 8081.
regards.

Resources