Ruby EventMachine testing - ruby

My first question concerning Ruby.
I'm trying to test EventMachine interaction inside the Reactor loop - I guess it could be classified as "functional" testing.
Say I have two classes - a server and a client. And I want to test both sides - I need to be sure about their interaction.
Server:
require 'singleton'
class EchoServer < EM::Connection
include EM::Protocols::LineProtocol
def post_init
puts "-- someone connected to the echo server!"
end
def receive_data data
send_data ">>>you sent: #{data}"
close_connection if data =~ /quit/i
end
def unbind
puts "-- someone disconnected from the echo server!"
end
end
Client:
class EchoClient < EM::Connection
include EM::Protocols::LineProtocol
def post_init
send_data "Hello"
end
def receive_data(data)
#message = data
p data
end
def unbind
puts "-- someone disconnected from the echo server!"
end
end
So, I've tried different approaches and came up with nothing.
The fundamental question is - could I somehow test my code with RSpec, using should_recive?
EventMachine parameter should be a class or a module, so I can't send instantiated/mocked code inside. Right?
Something like this?
describe 'simple rspec test' do
it 'should pass the test' do
EventMachine.run {
EventMachine::start_server "127.0.0.1", 8081, EchoServer
puts 'running echo server on 8081'
EchoServer.should_receive(:receive_data)
EventMachine.connect '127.0.0.1', 8081, EchoClient
EventMachine.add_timer 1 do
puts 'Second passed. Stop loop.'
EventMachine.stop_event_loop
end
}
end
end
And, if not, how would you do it with EM::SpecHelper? I have this code using it, and can't figure out what I'm doing wrong.
describe 'when server is run and client sends data' do
include EM::SpecHelper
default_timeout 2
def start_server
EM.start_server('0.0.0.0', 12345) { |ws|
yield ws if block_given?
}
end
def start_client
client = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
yield client if block_given?
return client
end
describe "examples from the spec" do
it "should accept a single-frame text message" do
em {
start_server
start_client { |client|
client.onopen {
client.send_data("\x04\x05Hello")
}
}
}
end
end
end
Tried a lot of variations of these tests and I just can't figure it out. I'm sure I'm missing something here...
Thanks for your help.

The simplest solution that I can think of is to change this:
EchoServer.should_receive(:receive_data)
To this:
EchoServer.any_instance.should_receive(:receive_data)
Since EM is expecting a class to start a server, the above any_instance trick will expect any instance of that class to receive that method.
The EMSpecHelper example (while being official/standard) is quite convoluted, I'd rather stick with the first rspec and use any_instance, just for simplicity's sake.

Related

EventMachine not receiving TCP data on localhost

Using eventmachine gem i am trying to send and receive data on localhost. Following is the code of my client and server files.
server.rb
class BCCServer < EM::Connection
attr_accessor :server_socket
def post_init
puts "BCC Server"
end
def recieve_data(data)
puts "Received data: #{data}"
send_data "You sent: #{data}"
end
end
EM.run do
EM.start_server("0.0.0.0", 3000, BCCServer)
end
client.rb
class DCClient < EventMachine::Connection
def post_init
puts "Sending "
send_data "send data"
close_connection_after_writing
end
def receive_data(data)
puts "Received #{data.length} bytes"
end
def unbind
puts 'Connection Lost !'
end
end
EventMachine.run do
EventMachine::connect("127.0.0.1", 3000, DCClient)
end
I executed both server and client files in separate console. Following is the output of client
Client output
Sending
Connection Lost !
Server output
BCC Server
............>>>10
In the server file i have printed the data received but its showing "............>>>10". Where i am doing the mistake?
Thanks
if you look at the EM::Connection implementation
https://github.com/eventmachine/eventmachine/blob/master/lib/em/connection.rb
def receive_data data
puts "............>>>#{data.length}"
end
Method receive_data returns exactly what you are experiencing.
That means original method gets called and not yours. That means one thing. You have a typo in a method which you tried to override :)
In BCCServer you have
recieve_data(data)
instead of
receive_data(data)

Faye ruby client publishing only once

I have a faye server (nodejs) running on localhost, and I am trying to setup a server side ruby client which needs to publish on the server on a regular basis. This is the code I am trying to use.
(Please ignore the commented code to start with).
I make a class variable ##client and initialize it as soon as the class loads. I define a class method pub whose task is to publish something on the faye server.
In the end, I just call the pub method twice. The first publication callback is received successfully, but the second publication doesn't make either of callback or the errback. And since the control has not been given back to the app, the app just hangs there.
If I make the gobal variable $client (currently commented), the behaviour is the same. But if I make the client everytime pub is called, then the publish goes on smoothly. I initiate it in EM.run loop or outside, the behavior is same. (as expected)
I don't want to make a new connection everytime I want to publish something since that defeats the purpose. Also, if I create a new client in EM.run everytime I call the method, the client connections don't close by themselves. I can see them open in lsof command as open files, and soon I'll start getting too many open files error I think.
I don't really understand Event Machine correctly, maybe I am missing something there.
require 'faye'
require 'eventmachine'
# $client = Faye::Client.new('http://localhost:5050/faye')
class Fayeclient
puts "#{__LINE__}: Reactor running: " + EM.reactor_running?.to_s
# if !defined? ##client or ##client.nil?
##client = Faye::Client.new('http://localhost:5050/faye')
puts "Created client: " + ##client.inspect
# end
def self.pub
puts "#{__LINE__}: Reactor running: " + EM.reactor_running?.to_s
# client = Faye::Client.new('http://localhost:5050/faye') #$client
# client = ##client
EM.run {
#client = Faye::Client.new('http://localhost:5050/faye') #$client
puts "#{__LINE__}: Reactor running: " + EM.reactor_running?.to_s
puts ##client.inspect
publication = ##client.publish('/foo', 'text' =>'Hello world')
puts "Publishing: #{publication.inspect}"
# puts "Publication methods: #{publication.methods}"
publication.callback do
puts "Did it #{publication.inspect}"
EM.stop_event_loop
puts "#{__LINE__}: Reactor running: " + EM.reactor_running?.to_s
# puts "#{client.methods}"
# puts client.inspect
# client.remove_all_listeners
# puts client.inspect
end
publication.errback do |error |
puts error.inspect
EM.stop_event_loop
end
}
puts "Outside event loop"
puts "#{__LINE__}: Reactor running: " + EM.reactor_running?.to_s
end
end
Fayeclient.pub
Fayeclient.pub
EM.run call is blocking, you have to run it on a separate thread, and eventually join it when all is over. In the example I'm using Singleton but it's up to you.
This does correctly the 2 faye calls.
#!/usr/bin/env ruby
#
require 'faye'
require 'singleton'
require 'eventmachine'
class Fayeclient
include Singleton
attr_accessor :em_thread, :client
def initialize
self.em_thread = Thread.new do
EM.run
end
self.client = Faye::Client.new('http://localhost:8890/faye')
end
def pub
puts "#{__LINE__}: Reactor running: " + EM.reactor_running?.to_s
puts client.inspect
publication = client.publish('/foo', 'text' =>'Hello world')
puts "Publishing: #{publication.inspect}"
publication.callback do
puts "Did it #{publication.inspect}"
EM.stop_event_loop
puts "#{__LINE__}: Reactor running: " + EM.reactor_running?.to_s
end
publication.errback do |error |
puts error.inspect
EM.stop_event_loop
end
end
end
Fayeclient.instance.pub
Fayeclient.instance.pub
Fayeclient.instance.em_thread.join
In my personal experience, anyway, having to deal with EventMachine inside Rails application can be a mess, some webserver uses EM, other does not so, and when you want to test from console it may not work as expected.
My solution is to fallback to http calls:
RestClient.post "http://localhost:#{Rails.configuration.faye_port}/faye", message: {foo: 'bar'}.to_json
I found this solution simpler and easy to customize, if you don't need to receive message from this piece of code.

How to test that a block is called within a thread?

I am working on wrapping the ruby-mqtt gem into a class which implements a subscribe and publish method. The subscribe method connects to the server and listens in a separate thread because this call is synchronous.
module PubSub
class MQTT
attr_accessor :host, :port, :username, :password
def initialize(params = {})
params.each do |attr, value|
self.public_send("#{attr}=", value)
end if params
super()
end
def connection_options
{
remote_host: self.host,
remote_port: self.port,
username: self.username,
password: self.password,
}
end
def subscribe(name, &block)
channel = name
connect_opts = connection_options
code_block = block
::Thread.new do
::MQTT::Client.connect(connect_opts) do |c|
c.get(channel) do |topic, message|
puts "channel: #{topic} data: #{message.inspect}"
code_block.call topic, message
end
end
end
end
def publish(channel = nil, data)
::MQTT::Client.connect(connection_options) do |c|
c.publish(channel, data)
end
end
end
end
I have a test that I have written using rspec to test the class but it does not pass.
mqtt = ::PubSub::MQTT.new({host: "localhost",port: 1883})
block = lambda { |channel, data| puts "channel: #{channel} data: #{data.inspect}"}
block.should_receive(:call).with("channel", {"some" => "data"})
thr = mqtt.subscribe("channel", &block)
mqtt.publish("channel", {"some" => "data"})
When I run the following ruby-mqtt-example I have now problems at all.
uri = URI.parse ENV['CLOUDMQTT_URL'] || 'mqtt://localhost:1883'
conn_opts = {
remote_host: uri.host,
remote_port: uri.port,
username: uri.user,
password: uri.password,
}
# Subscribe example
Thread.new do
puts conn_opts
MQTT::Client.connect(conn_opts) do |c|
# The block will be called when you messages arrive to the topic
c.get('test') do |topic, message|
puts "#{topic}: #{message}"
end
end
end
# Publish example
puts conn_opts
MQTT::Client.connect(conn_opts) do |c|
# publish a message to the topic 'test'
loop do
c.publish('test', 'Hello World')
sleep 1
end
end
So my question is, what am I doing wrong when I simply create a class and separate out the publish and subscribe logic? My guess is that it has something to do with Threading in the function call but I can't seem to figure it out. Any help is much appreciated.
UPDATE
I believe I know why the test is not passing and it is because when I pass a lambda in to subscribe expecting it to receive a call it actually will not receive the call when it exits the method or until publish is called. So I would like to rephrase the question to: How do I test that a block is called within a thread? If someone answers, "you don't", then the question is: How do you test that block is being called in an infinite loop like in the example of calling get within ruby-mqtt gem.
The RSpec expectations machinery will work fine with threads, as evidenced by the following example, which passes:
def foo(&block)
block.call(42)
end
describe "" do
it "" do
l = lambda {}
expect(l).to receive(:call).with(42)
Thread.new { foo(&l) }.join
end
end
The join waits for the thread(s) to finish before going further.

ruby-eventmachine use start_server with an instance

I'm new to ruby and maybe this is a very simple question..
I'd like to use eventmachine to develop a simulator for my tests.
Following example in documentation I can write something like this:
require 'eventmachine'
class Server< EM::Connection
def receive_data data
send_data data
close_connection_after_writing
end
end
#Note that this will block current thread.
EventMachine.run {
EventMachine.start_server '127.0.0.1','8080', Server
}
But I wonder if there is a way to use an instance of class, something like:
require 'eventmachine'
class Server< EM::Connection
attr_accessor :response
def receive_data data
send_data #response
close_connection_after_writing
end
end
server1 = Server.new
server1.response = "foo"
#Note that this will block current thread.
EventMachine.run {
EventMachine.start_server '127.0.0.1','8080', server1
}
I try to read source code..but it's too hard for me.
I'm surely missing something, but I don't know how to do something like this.
As I say there was something that I was missing.
You can add parameters for class to be instantiated :
class Server< EM::Connection
def initialize par
puts "I'm server number#{par}"
end
def receive_data data
send_data data
close_connection_after_writing
end
end
EventMachine.run {
EventMachine.start_server '127.0.0.1','8080', Server,1
}
EventMachine.run {
EventMachine.start_server '127.0.0.1','8080', Server,2
}
So I will customize instance behaviour with parameters

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