I know global variables aren't encouraged in Ruby but this is what I have been asked to do so you'll just have to go with me on this. I have a game that outputs messages to the command window successfully through the STDOUT. My task is to modify the class so that the messages are not only displayed to the STDOUT channel but also written to a buffer. This is so when I add an additional Sinatra method to the end of the file, the buffer is displayed in a browser (i.e localhost:4567).
So effectively, with the Sinatra gem called, running the spec.rb from a command window should result in the messages being displayed in the Web server in addition to the command window. But I do not know where to start in terms of outputting my messages to the buffer.
I'm pretty sure there is a very simple answer to this but my knowledge of ruby is not great. My thinking is I need to add a line to each activity concatenating the output of each to the global variable $buffer but how do I do this? Obviously following that, I need to write a Sinatra method that displays the contents of the global variable in the web browser.
Hope that makes sense.
I have two files, spec.rb and gen.rb. This is my code so far:
spec.rb
require "gen.rb"
module ImpossibleMachine
# Input and output constants processed by subprocesses
DOWN_ARROW = 1
UP_ARROW = 2
RIGHT_ARROW = 3
REPEAT_ARROW = 4
END_PROCESS = 5
START_CURRENT = 6
# RSpec Tests
describe Game do
describe "#start The impossible machine game" do
before(:each) do
#process = []
#output = double('output').as_null_object
#game = Game.new(#output)
end
it "sends a welcome message" do
#output.should_receive(:puts).with('Welcome to the Impossible Machine!')
#game.start
end
it "sends a starting message" do
#output.should_receive(:puts).with('Starting game...')
#game.start
end
it "should perform lifts_lever_turns_wheel activity which returns REPEAT_ARROW" do
#output.should_receive(:puts).with("Input: #{UP_ARROW}, Activity: Heave_ho_squeek_squeek")
#process[1] = #game.lifts_lever_turns_wheel(UP_ARROW)
#process[1].should == REPEAT_ARROW
end
it "should perform turns_tap_on_pulls_down_seesaw activity which returns DOWN_ARROW" do
#output.should_receive(:puts).with("Input: #{REPEAT_ARROW}, Activity: Drip_drip_creek_creek")
#process[2] = #game.turns_tap_on_pulls_down_seesaw(REPEAT_ARROW)
#process[2].should == DOWN_ARROW
end
it "should perform pulls_down_seezaw_starts_current activity which returns START_CURRENT" do
#output.should_receive(:puts).with("Input: #{DOWN_ARROW}, Activity: Creek_creek_buzz_buzz")
#process[2] = #game.pulls_down_seezaw_starts_current(DOWN_ARROW)
#process[2].should == START_CURRENT
end
it "should perform starts_current_pushes_grove activity which returns RIGHT_ARROW" do
#output.should_receive(:puts).with("Input: #{START_CURRENT}, Activity: Buzz_buzz_pow_wallop")
#process[3] = #game.starts_current_pushes_grove(START_CURRENT)
#process[3].should == RIGHT_ARROW
end
it "sends a finishing message" do
#output.should_receive(:puts).with('...Game finished.')
#game.finish
end
end
end
end
gen.rb
require 'sinatra'
$buffer = ""
# Main class module
module ImpossibleMachine
# Input and output constants processed by subprocesses. MUST NOT change.
DOWN_ARROW = 1
UP_ARROW = 2
RIGHT_ARROW = 3
REPEAT_ARROW = 4
END_PROCESS = 5
START_CURRENT = 6
class Game
attr_reader :process, :output
attr_writer :process, :output
def initialize(output)
#output = output
puts "[#{#output}]"
end
# All the code/methods aimed at passing the RSpect tests are below.
def start
#output.puts'Welcome to the Impossible Machine!'
#output.puts'Starting game...'
end
def lifts_lever_turns_wheel(input)
#input = input
#output.puts 'Input: 2, Activity: Heave_ho_squeek_squeek'
return REPEAT_ARROW
end
def turns_tap_on_pulls_down_seesaw(input)
#input = input
#output.puts 'Input: 4, Activity: Drip_drip_creek_creek'
return DOWN_ARROW
end
def pulls_down_seezaw_starts_current(input)
#input = input
#output.puts 'Input: 1, Activity: Creek_creek_buzz_buzz'
return START_CURRENT
end
def starts_current_pushes_grove(input)
#input = input
#output.puts 'Input: 6, Activity: Buzz_buzz_pow_wallop'
return RIGHT_ARROW
end
def finish
#output.puts'...Game finished.'
end
end
end
# Main program
module ImpossibleMachine
#process = []
g = Game.new(STDOUT)
# All code added to output the activity messages to the command line window is below.
g.start
#process[0] = g.lifts_lever_turns_wheel(2)
#process[1] = g.turns_tap_on_pulls_down_seesaw(#process[0])
#process[2] = g.pulls_down_seezaw_starts_current(#process[1])
#process[3] = g.starts_current_pushes_grove(#process[2])
g.finish
end
# Any sinatra code added to output the activity messages to a browser should be added below.
# End program
managed to get this to work after hours!
At the top add/adjust to:
require 'stringio'
$buffer= StringIO.new
in the main program:
g = Game.new($buffer)
g.start
#process[0] = g.lifts_lever_turns_wheel(2)
etc......
g.finish
puts $buffer.string #this sends it to stdout
then just add in the sinatra coding at the bottom which will also use $buffer.string
possibly not the best or smartest way to do it but it uses the global buffer they wanted and gets it to sinatra and the cmd line.
I think you could just create some sort of buffer object in your main program and pass it in the game, just like you pass in STDOUT. Then you could call write methods on the passed in object inside the game.
Related
I'm trying to write a test for a case statement using minitest. Would I need to write separate tests for each "when"? I included my code below. Right now it just puts statements, but eventually it's going to redirect users to different methods. Thanks!
require 'pry'
require_relative 'messages'
class Game
attr_reader :user_answer
def initialize(user_answer = gets.chomp.downcase)
#user_answer = user_answer
end
def input
case user_answer
when "i"
puts "information"
when "q"
puts "quitter"
when "p"
puts "player play"
end
end
end
This answer will help you. Nonetheless I'll post one way of applying it to your situation. As suggested by #phortx when initializing a game, override the default user-input with the relevant string. Then by using assert_output we can do something like:
#test_game.rb
require './game.rb' #name and path of your game script
require 'minitest/autorun' #needed to run tests
class GameTest < MiniTest::Test
def setup
#game_i = Game.new("i") #overrides default user-input
#game_q = Game.new("q")
#game_p = Game.new("p")
end
def test_case_i
assert_output(/information\n/) {#game_i.input}
end
def test_case_q
assert_output(/quitter\n/) {#game_q.input}
end
def test_case_p
assert_output(/player play\n/) {#game_p.input}
end
end
Running the tests...
$ ruby test_game.rb
#Run options: --seed 55321
## Running:
#...
#Finished in 0.002367s, 1267.6099 runs/s, 2535.2197 assertions/s.
#3 runs, 6 assertions, 0 failures, 0 errors, 0 skips
You have to test each case branch. Via RSpec it would work that way:
describe Game do
subject { Game }
describe '#input' do
expect_any_instance_of(Game).to receive(:puts).with('information')
Game.new('i').input
expect_any_instance_of(Game).to receive(:puts).with('quitter')
Game.new('q').input
expect_any_instance_of(Game).to receive(:puts).with('player play')
Game.new('p').input
end
end
However due the fact that puts is ugly to test, you should refactor your code to something like that:
require 'pry'
require_relative 'messages'
class Game
attr_reader :user_answer
def initialize(user_answer = gets.chomp.downcase)
#user_answer = user_answer
end
def input
case user_answer
when "i"
"information"
when "q"
"quitter"
when "p"
"player play"
end
end
def print_input
puts input
end
end
Then you can test with RSpec via:
describe Game do
subject { Game }
describe '#print_input' do
expect_any_instance_of(Game).to receive(:puts).with('quitter')
Game.new('q').print_input
end
describe '#input' do
expect(Game.new('i').input).to eq('information')
expect(Game.new('q').input).to eq('quitter')
expect(Game.new('i').input).to eq('player play')
expect(Game.new('x').input).to eq(nil)
end
end
I am implementing a simple program in Celluloid that ideally will run a few actors in parallel, each of which will compute something, and then send its result back to a main actor, whose job is simply to aggregate results.
Following this FAQ, I introduced a SupervisionGroup, like this:
module Shuffling
class AggregatorActor
include Celluloid
def initialize(shufflers)
#shufflerset = shufflers
#results = {}
end
def add_result(result)
#results.merge! result
#shufflerset = #shufflerset - result.keys
if #shufflerset.empty?
self.output
self.terminate
end
end
def output
puts #results
end
end
class EvalActor
include Celluloid
def initialize(shufflerClass)
#shuffler = shufflerClass.new
self.async.runEvaluation
end
def runEvaluation
# computation here, which yields result
Celluloid::Actor[:aggregator].async.add_result(result)
self.terminate
end
end
class ShufflerSupervisionGroup < Celluloid::SupervisionGroup
shufflers = [RubyShuffler, PileShuffle, VariablePileShuffle, VariablePileShuffleHuman].to_set
supervise AggregatorActor, as: :aggregator, args: [shufflers.map { |sh| sh.new.name }]
shufflers.each do |shuffler|
supervise EvalActor, as: shuffler.name.to_sym, args: [shuffler]
end
end
ShufflerSupervisionGroup.run
end
I terminate the EvalActors after they're done, and I also terminate the AggregatorActor when all of the workers are done.
However, the supervision thread stays alive and keeps the main thread alive. The program never terminates.
If I send .run! to the group, then the main thread terminates right after it, and nothing works.
What can I do to terminate the group (or, in group terminology, finalize, I suppose) after the AggregatorActor terminates?
What I did after all, is change the AggregatorActor to have a wait_for_results:
class AggregatorActor
include Celluloid
def initialize(shufflers)
#shufflerset = shufflers
#results = {}
end
def wait_for_results
sleep 5 while not #shufflerset.empty?
self.output
self.terminate
end
def add_result(result)
#results.merge! result
#shufflerset = #shufflerset - result.keys
puts "Results for #{result.keys.inspect} recorded, remaining: #{#shufflerset.inspect}"
end
def output
puts #results
end
end
And then I got rid of the SupervisionGroup (since I didn't need supervision, ie rerunning of actors that failed), and I used it like this:
shufflers = [RubyShuffler, PileShuffle, VariablePileShuffle, VariablePileShuffleHuman, RiffleShuffle].to_set
Celluloid::Actor[:aggregator] = AggregatorActor.new(shufflers.map { |sh| sh.new.name })
shufflers.each do |shuffler|
Celluloid::Actor[shuffler.name.to_sym] = EvalActor.new shuffler
end
Celluloid::Actor[:aggregator].wait_for_results
That doesn't feel very clean, it would be nice if there was a cleaner way, but at least this works.
I'm having trouble understanding how to test for output with puts. I need to know what I need to do in my RSPEC file.
This is my RSPEC file:
require 'game_io'
require 'board'
describe GameIO do
before(:each) do
#gameio = GameIO.new
#board = Board.new
end
context 'welcome_message' do
it 'should display a welcome message' do
test_in = StringIO.new("some test input\n")
test_out = StringIO.new
test_io = GameIO.new(test_in, test_out)
test_io.welcome_message
test_io.game_output.string.should == "Hey, welcome to my game. Get ready to be defeated"
end
end
end
This is the file it is testing against:
class GameIO
attr_reader :game_input, :game_output
def initialize(game_input = $stdin, game_output = $stdout)
#stdin = game_input
#stdout = game_output
end
def welcome_message
output "Hey, welcome to my game. Get ready to be defeated"
end
def output(msg)
#stdout.puts msg
end
def input
#stdin.gets
end
end
NOTE: I updated my RSPEC code to reflect changes I made to my test file given suggestions found elsewhere. To resolve the poblem completly I used the changes suggested by Chris Heald in my main file. Thank you all and thank you Chris.
Your initializer should be:
def initialize(game_input = $stdin, game_output = $stdout)
#game_input = game_input
#game_output = game_output
end
The reason for this is that attr_accessor generates methods like this:
# attr_accessor :game_output
def game_output
#game_output
end
def game_output=(output)
#game_output = output
end
(attr_reader generates only the reader method)
Thus, since you never assign #game_output, your game_output method will always return nil.
Just check you are sending it the message:
#gameio.should_receive(:puts).with("Hey, welcome to my game. Get ready to be defeated")
You could stub puts and print.
Perhaps the most fundamental way is to temporarily reassign STDOUT to a variable, and confirm the variable matches what you expect for output.
And Minitest has must_output as an assertion/spec.
The code is thus:
##
# Fails if stdout or stderr do not output the expected results.
# Pass in nil if you don't care about that streams output. Pass in
# "" if you require it to be silent. Pass in a regexp if you want
# to pattern match.
#
# NOTE: this uses #capture_io, not #capture_subprocess_io.
#
# See also: #assert_silent
def assert_output stdout = nil, stderr = nil
out, err = capture_io do
yield
end
err_msg = Regexp === stderr ? :assert_match : :assert_equal if stderr
out_msg = Regexp === stdout ? :assert_match : :assert_equal if stdout
y = send err_msg, stderr, err, "In stderr" if err_msg
x = send out_msg, stdout, out, "In stdout" if out_msg
(!stdout || x) && (!stderr || y)
end
I use one rb file with rufus/scheduler on Windows. The script is executed on a comupter start up and it runs in a cmd window.
How can I log everything that ruby outputs to the screen to a file? I still want to be able to see the output on the screen. So I want the logging on top of current behaviour.
Windows 7 64 bit
ruby 1.9.3p194 (2012-04-20) [i386-mingw32]
If you just want the script to send output to the file instead of the console use IO#reopen to redirect stdout and stderr.
def redirect_console(filename)
$stdout.reopen(filename,'w')
$stderr.reopen(filename,'w')
end
redirect_console('/my/console/output/file')
If you need to direct to one or more output streams, use a proxy object and method_missing to send to them
class TeeIO
def initialize(*streams)
raise ArgumentError, "Can only tee to IO objects" unless streams.all? { |e| e.is_a? IO }
#streams = streams
end
def method_missing(meth, *args)
# HACK only returns result of first stream
#streams.map {|io| io.send(meth, *args) }.first
end
def respond_to_missing?(meth, include_all)
#streams.all? {|io| io.respond_to?(meth, include_all) }
end
end
def tee_console(filename)
tee_to = File.open(filename, 'w')
tee_to.sync = true # flush after each write
$stdout = TeeIO.new($stdout, tee_to)
$stderr = TeeIO.new($stderr, tee_to)
end
tee_console('/my/console/output/file')
I'm reading a Redis set within an EventMachine reactor loop using a suitable Redis EM gem ('em-hiredis' in my case) and have to check if some Redis sets contain members in a cascade. My aim is to get the name of the set which is not empty:
require 'eventmachine'
require 'em-hiredis'
def fetch_queue
#redis.scard('todo').callback do |scard_todo|
if scard_todo.zero?
#redis.scard('failed_1').callback do |scard_failed_1|
if scard_failed_1.zero?
#redis.scard('failed_2').callback do |scard_failed_2|
if scard_failed_2.zero?
#redis.scard('failed_3').callback do |scard_failed_3|
if scard_failed_3.zero?
EM.stop
else
queue = 'failed_3'
end
end
else
queue = 'failed_2'
end
end
else
queue = 'failed_1'
end
end
else
queue = 'todo'
end
end
end
EM.run do
#redis = EM::Hiredis.connect "redis://#{HOST}:#{PORT}"
# How to get the value of fetch_queue?
foo = fetch_queue
puts foo
end
My question is: how can I tell EM to return the value of 'queue' in 'fetch_queue' to use it in the reactor loop? a simple "return queue = 'todo'", "return queue = 'failed_1'" etc. in fetch_queue results in "unexpected return (LocalJumpError)" error message.
Please for the love of debugging use some more methods, you wouldn't factor other code like this, would you?
Anyway, this is essentially what you probably want to do, so you can both factor and test your code:
require 'eventmachine'
require 'em-hiredis'
# This is a simple class that represents an extremely simple, linear state
# machine. It just walks the "from" parameter one by one, until it finds a
# non-empty set by that name. When a non-empty set is found, the given callback
# is called with the name of the set.
class Finder
def initialize(redis, from, &callback)
#redis = redis
#from = from.dup
#callback = callback
end
def do_next
# If the from list is empty, we terminate, as we have no more steps
unless #current = #from.shift
EM.stop # or callback.call :error, whatever
end
#redis.scard(#current).callback do |scard|
if scard.zero?
do_next
else
#callback.call #current
end
end
end
alias go do_next
end
EM.run do
#redis = EM::Hiredis.connect "redis://#{HOST}:#{PORT}"
finder = Finder.new(redis, %w[todo failed_1 failed_2 failed_3]) do |name|
puts "Found non-empty set: #{name}"
end
finder.go
end