Why is my current stub in my RSpec test not properly stubbing the call to the method? Currently getting No such file or directory # rb_sysopen - ruby

Ruby Version 2.7.2
Rspec Version 3.12.0
So I'm currently working through App Academy Open and I'm at the point where we're creating a tic tac toe game. I've written out all my tests and they pass except for the last few.
I have successfully stubbed method calls in the past but for whatever reason, I'm not getting it to work here.
I have 3 classes Board, HumanPlayer, and Game. The method I am currently testing is #play from within the Gameclass:
def play
while #board.empty_positions?
puts #board.print
position = #current_player.get_position
#board.place_mark(position, #current_player.mark)
if #board.win?(#current_player.mark)
puts "Player #{#current_player.mark} wins!"
return
else
switch_turn
end
end
puts "The game ended in a draw!"
end
Here is what my test looks like:
RSpec.describe Game do
let(:game) { Game.new(:X, :O) }
# ...
describe "#play" do
before :each do
#board = game.instance_variable_get(:#board)
#board.place_mark([0, 0], :X)
#board.place_mark([0, 1], :O)
#board.place_mark([0, 2], :X)
#board.place_mark([1, 0], :O)
#board.place_mark([2, 0], :X)
#board.place_mark([1, 1], :O)
#board.place_mark([2, 2], :X)
end
it "should call Board#place_mark" do
#current_player = game.instance_variable_get(:#current_player)
allow(#current_player).to receive(:get_position).and_return([1, 2])
expect(#board).to receive(:place_mark)
game.play
end
end
end
Here is the HumanPlayer#get_position method:
def get_position
puts "Player #{#mark}, enter two numbers representing a position in the format `row col`"
position = gets.chomp.split(" ")
if position.length != 2 || # not 2 characters
position.any? { |n| n.to_i.to_s != n } # not all numeric
raise "Invalid Position"
end
position.map(&:to_i)
end
Here is the Board#place_mark method:
def place_mark(position, mark)
raise "Placement Invalid" if !valid?(position) || !empty?(position)
row = position[0]
col = position[1]
#grid[row][col] = mark
end
So whenever I run the tests I always get the error:
Game Instance Methods #play should call Board#place_mark
Failure/Error: position = gets.chomp.split(" ")
Errno::ENOENT:
No such file or directory # rb_sysopen - spec/2_game_spec.rb:85
# ./lib/human_player.rb:11:in `gets'
# ./lib/human_player.rb:11:in `gets'
# ./lib/human_player.rb:11:in `get_position'
# ./lib/game.rb:20:in `play'
# ./spec/2_game_spec.rb:92:in `block (4 levels) in <top (required)>'
I believe I'm stubbing the HumanPlayer.get_position method to return [1, 2] when called but for whatever reason, the Board.place_mark method does not successfully place the piece on the board and thus, the HumanPlayer.get_position gets called again because of the loop and when it hits the gets call, it produces that error output.
I've tried stubbing the gets call with this:
it "should call Board#place_mark" do
#current_player = game.instance_variable_get(:#current_player)
allow(#current_player).to receive(:gets).and_return("1 2")
expect(#board).to receive(:place_mark)
game.play
end
I also tired allow_any_instance_of(HumanPlayer) but it just prints the board in an endless loop:
it "should call Board#place_mark" do
#current_player = game.instance_variable_get(:#current_player)
allow_any_instance_of(HumanPlayer).to receive(:get_position).and_return([1, 2])
expect(#board).to receive(:place_mark)
game.play
end
This is my first question on SO, so if there is anything I need to add please let me know. Thanks in advance.

If you can't figure out the Errno::ENOENT error, you might try a slightly different design, which I think is easier to stub.
class GameCLI
def gets
Kernel.gets
end
end
class HumanPlayer
def get_position
# ..
cli = GameCLI.new # or, pass cli instance as argument to e.g. get_position
position = cli.gets # ..
# ..
end
end
RSpec.describe HumanPlayer do
describe '#get_position' do
it '..' do
allow_any_instance_of(GameCLI).to receive(:gets).and_return('..')
# ..
end
end
end

Related

How do I get ruby to print a full backtrace that includes arguments passed to functions?

Sometimes backtrace is enough to diagnose problem. But sometimes reason of crash is not obvious without knowledge what was passed to function.
Getting information what was passed to function that caused crash would be quite useful, especially in cases where reproducing is not obvious because it was caused by for example exception in network connection, weird user input or because program is depends on randomisation or processes data from external sensor.
Lets say that there is following program
def handle_changed_input(changed_input)
raise 'ops' if changed_input =~ /magic/
end
def do_something_with_user_input(input)
input = "#{input.strip}c"
handle_changed_input(input)
end
input = gets
do_something_with_user_input(input)
where user typed "magic" as input. Normally one has
test.rb:2:in `handle_changed_input': ops (RuntimeError)
from test.rb:7:in `do_something_with_user_input'
from test.rb:11:in `<main>'
as output. What one may do to show also what was passed to function? Something like
test.rb:2:in `handle_changed_input("magic")': ops (RuntimeError)
from test.rb:7:in `do_something_with_user_input("magi\n")'
from test.rb:11:in `<main>'
It would be useful in many situations (and not truly useful where parameters are not representable as strings of reasonable legth, there is a good reason why it is not enabled by default).
How one may add this functionality? It is necessary that program works as usually during normal operation and preferably there is no additional output before crash.
I tried for example
def do_something_with_user_input(input)
method(__method__).parameters.map do |_, name|
puts "#{name}=#{binding.local_variable_get(name)}"
end
raise 'ops' if input =~ /magic/
end
input = gets
found in Is there a way to access method arguments in Ruby? but it would print on every single entrance to function what both would flood output and make program significantly slower.
I don't have a complete solution but... But you can get method arguments of all called methods in controlled environment with TracePoint class from Ruby core lib.
Look at the example:
trace = TracePoint.new(:call) do |tp|
puts "===================== #{tp.method_id}"
b_self = tp.binding.eval('self')
names = b_self.method(tp.method_id).parameters.map(&:last)
values = names.map { |name| tp.binding.eval(name.to_s) }
p names.zip(values)
end
trace.enable
def method_a(p1, p2, p3)
end
method_a(1, "foobar", false)
#=> ===================== method_a
#=> [[:p1, 1], [:p2, "foobar"], [:p3, false]]
To print exception backtraces, Ruby uses the C function exc_backtrace from error.c (exc_backtrace on github). Unless you patch Ruby with the functionality you need, I don't think there a way to change exception backtrace outputs.
Here is a snippet (trace.rb) you might find useful:
set_trace_func -> (event, file, line, id, binding, classname) do
if event == 'call' && meth = binding.eval('__method__')
params = binding.method(meth).parameters.select{|e| e[0] != :block}
values = params.map{|_, var| [var, binding.local_variable_get(var)]}
printf "%8s %s:%-2d %15s %8s %s\n", event, file, line, id, classname, values.inspect
else
printf "%8s %s:%-2d %15s %8s\n", event, file, line, id, classname
end
end
def foo(a,b = 0)
bar(a, foo: true)
end
def bar(c, d = {})
puts "!!!buz!!!\n"
end
foo('lol')
The output of that snippet is:
c-return /path/to/trace.rb:1 set_trace_func Kernel
line /path/to/trace.rb:12
c-call /path/to/trace.rb:12 method_added Module
c-return /path/to/trace.rb:12 method_added Module
line /path/to/trace.rb:16
c-call /path/to/trace.rb:16 method_added Module
c-return /path/to/trace.rb:16 method_added Module
line /path/to/trace.rb:20
call /path/to/trace.rb:12 foo Object [[:a, "lol"], [:b, 0]]
line /path/to/trace.rb:13 foo Object
call /path/to/trace.rb:16 bar Object [[:c, "lol"], [:d, {:foo=>true}]]
line /path/to/trace.rb:17 bar Object
c-call /path/to/trace.rb:17 puts Kernel
c-call /path/to/trace.rb:17 puts IO
c-call /path/to/trace.rb:17 write IO
!!!buz!!!
c-return /path/to/trace.rb:17 write IO
c-return /path/to/trace.rb:17 puts IO
c-return /path/to/trace.rb:17 puts Kernel
return /path/to/trace.rb:18 bar Object
return /path/to/trace.rb:14 foo Object
I hope that helps you as much as it helped me.
I think that it is possible. The code below is not perfect and would require some additional work, but it caputers the primary idea of a stacktrace with argument values. Please note, that in order to know the call site, I am zipping the original stacktrace with the entry sites catched by trace function. To distinguishe these entries I use '>' and '<' respectively.
class Reporting
def self.info(arg1)
puts "*** #{arg1} ***"
end
end
def read_byte(arg1)
Reporting.info(arg1)
raise Exception.new("File not found")
end
def read_input(arg1)
read_byte(arg1)
end
def main(arg1)
read_input(arg1)
end
class BetterStacktrace
def self.enable
set_trace_func -> (event, file, line, id, binding, classname) do
case event
when 'call'
receiver_type = binding.eval('self.class')
if receiver_type == Object
meth = binding.eval('__method__')
params = binding.method(meth).parameters.select{|e| e[0] != :block}
values = params.map{|_, var| [var, binding.local_variable_get(var)]}
self.push(event, file, line, id, classname, values)
else
self.push(event, file, line, id, classname)
end
when 'return'
self.pop
when 'raise'
self.push(event, file, line, id, classname)
Thread.current[:_keep_stacktrace] = true
end
end
end
def self.push(event, file, line, id, classname, values=nil)
Thread.current[:_saved_stacktrace] = [] unless Thread.current.key?(:_saved_stacktrace)
unless Thread.current[:_keep_stacktrace]
if values
values_msg = values.map(&:last).join(", ")
msg = "%s:%d:in `%s(%s)'" % [file, line, id, values_msg]
else
msg = "%s:%d:in `%s'" % [file, line, id]
end
Thread.current[:_saved_stacktrace] << msg
end
end
def self.pop()
Thread.current[:_saved_stacktrace] = [] unless Thread.current.key?(:_saved_stacktrace)
unless Thread.current[:_keep_stacktrace]
value = Thread.current[:_saved_stacktrace].pop
end
end
def self.disable
set_trace_func nil
end
def self.print_stacktrace(calls)
enters = Thread.current[:_saved_stacktrace].reverse
calls.zip(enters).each do |call, enter|
STDERR.puts "> #{enter}"
STDERR.puts "< #{call}"
end
Thread.current[:_saved_stacktrace] = []
end
end
BetterStacktrace.enable
begin
main(10)
rescue Exception => ex
puts "--- Catched ---"
puts ex
BetterStacktrace.print_stacktrace(ex.backtrace)
end
BetterStacktrace.disable
begin
main(10)
rescue Exception
puts "--- Catched ---"
puts ex
puts ex.backtrace
end
The output of the above code is as follows:
*** 10 ***
--- Catched ---
File not found
> work/tracing_with_params.rb:10:in `read_byte'
< work/tracing_with_params.rb:10:in `read_byte'
> work/tracing_with_params.rb:8:in `read_byte(10)'
< work/tracing_with_params.rb:14:in `read_input'
> work/tracing_with_params.rb:13:in `read_input(10)'
< work/tracing_with_params.rb:18:in `main'
> work/tracing_with_params.rb:17:in `main(10)'
< work/tracing_with_params.rb:82:in `<main>'
*** 10 ***
--- Catched ---
File not found
work/tracing_with_params.rb:10:in `read_byte'
work/tracing_with_params.rb:14:in `read_input'
work/tracing_with_params.rb:18:in `main'
work/tracing_with_params.rb:82:in `<main>'
EDIT:
The calls to class functions are not recorded. This has to be fixed in order for the stacktrace printing function not to get invalid output.
Moreover I used the STDERR as output to easily get one or the other output. You can change it if you wish.
MAX_STACK_SIZE = 200
tracer = proc do |event|
if event == 'call' && caller_locations.length > MAX_STACK_SIZE
fail "Probable Stack Overflow"
end
end
set_trace_func(tracer)

Ruby: Chatterbot can't load bot data

I'm picking up ruby language and get stuck at playing with the chatterbot i have developed. Similar issue has been asked here Click here , I did what they suggested to change the rescue in order to see the full message.But it doesn't seem right, I was running basic_client.rb at rubybot directory and fred.bot is also generated at that directory . Please see the error message below: Your help very be very much appreciated.
Snailwalkers-MacBook-Pro:~ snailwalker$ cd rubybot
Snailwalkers-MacBook-Pro:rubybot snailwalker$ ruby basic_client.rb
/Users/snailwalker/rubybot/bot.rb:12:in `rescue in initialize': Can't load bot data because: No such file or directory - bot_data (RuntimeError)
from /Users/snailwalker/rubybot/bot.rb:9:in `initialize'
from basic_client.rb:3:in `new'
from basic_client.rb:3:in `<main>'
basic_client.rb
require_relative 'bot.rb'
bot = Bot.new(:name => 'Fred', :data_file => 'fred.bot')
puts bot.greeting
while input = gets and input.chomp != 'end'
puts '>> ' + bot.response_to(input)
end
puts bot.farewell
bot.rb:
require 'yaml'
require './wordplay'
class Bot
attr_reader :name
def initialize(options)
#name = options[:name] || "Unnamed Bot"
begin
#data = YAML.load(File.read('bot_data'))
rescue => e
raise "Can't load bot data because: #{e}"
end
end
def greeting
random_response :greeting
end
def farewell
random_response :farewell
end
def response_to(input)
prepared_input = preprocess(input).downcase
sentence = best_sentence(prepared_input)
reversed_sentence = WordPlay.switch_pronouns(sentence)
responses = possible_responses(sentence)
responses[rand(responses.length)]
end
private
def possible_responses(sentence)
responses = []
#data[:responses].keys.each do |pattern|
next unless pattern.is_a?(String)
if sentence.match('\b' + pattern.gsub(/\*/, '') + '\b')
if pattern.include?('*')
responses << #data[:responses][pattern].collect do |phrase|
matching_section = sentence.sub(/^.*#{pattern}\s+/, '')
phrase.sub('*', WordPlay.switch_pronouns(matching_section))
end
else
responses << #data[:responses][pattern]
end
end
end
responses << #data[:responses][:default] if responses.empty?
responses.flatten
end
def preprocess(input)
perform_substitutions input
end
def perform_substitutions(input)
#data[:presubs].each {|s| input.gsub!(s[0], s[1])}
input
end
# select best_sentence by looking at longest sentence
def best_sentence(input)
hot_words = #data[:responses].keys.select do |k|
k.class == String && k =~ /^\w+$/
end
WordPlay.best_sentence(input.sentences, hot_words)
end
def random_response(key)
random_index = rand(#data[:responses][key].length)
#data[:responses][key][random_index].gsub(/\[name\]/, #name)
end
end
I'm assuming that you are trying to load the :data_file passed into Bot.new, but right now you are statically loading a bot_data file everytime. You never mentioned about bot_data in the question. So if I'm right it should be like this :
#data = YAML.load(File.read(options[:data_file]))
Instead of :
#data = YAML.load(File.read('bot_data'))

Send messages to a global buffer in Ruby

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.

Undefined method error using a Proc with ruby, cucumber and rspec

Looks like my use of the proc maybe a bit off. I'm working on a tic-tac-toe game and using cucumber to test it's behavior. I've outlined the scenario that i want to fulfill and the step file that I'm using.
The Scenario,
Scenario: Making Bad Moves
Given I have a started Tic-Tac-Toe game
And it is my turn
And I am playing X
When I enter a position "A1" on the board
And "A1" is taken
Then computer should ask me for another position "B2"
And it is now the computer's turn
The step files say...
Given /^I have a started Tic\-Tac\-Toe game$/ do #
#game = TicTacToe.new(:player)
#game.player = "player" #
end
Given /^it is my turn$/ do #
#game.current_player.should eq "player"
end
Given /^I am playing X$/ do
#game = TicTacToe.new(:computer, :X)
#game.player_symbol.should eq :X
end
When /^"(.*?)" is taken$/ do |arg1|
#game.board[arg1.to_sy m] = :O **- # this is causing me to get a "undefined
method `[]=' for #
Given /^I am playing X$/ do
#game = TicTacToe.new(:computer, :X)
#game.player_symbol.should eq :X
end
My code that is attempting to satisfy the feature is:
def board
Proc.new do |get_player_move = :B2|
board_value = get_player_move
#board_locations[board_value]
end
I get this error: NoMethodError: undefined method[]=' for #`
Am i using the proc properly?
Your problem is that [] and []= are in fact different methods. When you type:
#game.board[arg1.to_sym] = :O
ruby reads it as:
#game.board.[]=(arg1.to_sym, :o)
and what you want is
#game.board.[](arg1.to_sym) = :O
To make sure ruby knows what you want do:
(#game.board[arg1.to_sym]) = :O
NOTE:
To be honest I am not sure why you are using Proc here at all, why not simple:
def board
#board_locations
end

Undefined local variable or method 'product'

I am doing a task that requires me add some products together and give a 10% discount providing the total is above £60. I have done the following:
class Checkout
def initialize (rules)
#rules = rules
#cart = []
end
def scan (item)
if product == Product.find(item)
#cart << product.clone
#Clone preserves frozen state whereas .dup() doesn't if use would raise a
#NoMethodError
end
end
def total
#cart = #rules.apply #cart
end
def self.find item
[item]
end
co = Checkout.new(Promotional_Rules.new)
co.empty_cart
co.scan(1)
co.scan(2)
co.scan(3)
puts "Total price: #{co.total}"
puts
co.empty_cart
co.scan(1)
co.scan(3)
co.scan(1)
puts "Total price: #{co.total}"
puts
co.empty_cart
co.scan(1)
co.scan(2)
co.scan(1)
co.scan(3)
puts "Total price: #{co.total}"
puts
However when I run this in irb I get undefined variable or method product. Sounds a bit daft but this should work.
You're using one too many equal signs
def scan (item)
# if product == Product.find(item)
if product = Product.find(item) # <--- should be this
#cart << product.clone
#Clone preserves frozen state whereas .dup() doesn't if use would raise a
#NoMethodError
end
end
Of course, then you'll get a different error since find doesn't exist on Product yet... which I think you're trying to define here:
def self.find item # self should be changed to Product
[item]
end
Then you're going to get an error for apply not existing for Promotional_Rules ...
One of the best ways to debug these errors is follow the stack traces. So for the last error I get the following message:
test.rb:53:in `total': undefined method `apply' for #<Promotional_Rules:0x007f94f48bc7a8> (NoMethodError)
from test.rb:72:in `<main>'
That's basically saying that at line 53 you'll find apply hasn't been defined for #rules which is an instance of Promotional_Rules. Looking at the Promotional_Rules class you've clearly defined that method as apply_to_item and not apply. If you keep following and fixing the rabbit trails like this for stack traces you'll be able to debug your program with ease!

Resources