Rspec test cases not working - ruby

I probably have done some silly mistake but by running rake shows this
Due to infinite loop it runs infinitely until i stopped it.
The folder structure follows :
calculator.rb
class Calculator
attr_accessor :result
def initialize
#result = 0.0
end
def add(param)
#result += param
end
def subtract(param)
#result -= param
end
def multiply(param)
#result *= param
end
def divide(param)
#result /= param
end
def cancel
#result = 0.0
end
end
class CommandProcessor
attr_accessor :input
attr_accessor :operation
attr_accessor :calculator
def parser
calculator = Calculator.new
while true
input = gets.to_s.chomp
operation = input.split(' ')[0]
param = input.split(' ')[1]
if operation.eql? 'exit'
exit
elsif operation.eql? 'add'
calculator.add(param.to_f)
puts calculator.result
elsif operation.eql? 'subtract'
calculator.subtract(param.to_f)
puts calculator.result
elsif operation.eql? 'multiply'
calculator.multiply(param.to_f)
puts calculator.result
elsif operation.eql? 'divide'
calculator.divide(param.to_f)
puts calculator.result
elsif operation.eql? 'cancel'
calculator.cancel
puts calculator.result
else
puts "invalid op"
end
end
end
end
command = CommandProcessor.new
command.parser
calculator_spec.rb
require 'spec_helper'
require "calculator.rb"
describe "CommandProcessor" do
it "will exit on input exit" do
#cmd = CommandProcessor.new
#cmd.stub!(:gets).and_return("add 3\n")
#cmd.parser
expect(#cmd.calculator.result).to eq 3
end
end
describe "Calculator" do
it "will add a number" do
calculator = Calculator.new
expect(calculator.add 2).to eq 2.0
expect(calculator.add 2.0).to eq 4.0
end
it "will subtract a number" do
calculator = Calculator.new
expect(calculator.subtract 2).to eq -2.0
expect(calculator.subtract 2.0).to eq -4.0
end
it "will multiply a number" do
calculator = Calculator.new
expect(calculator.multiply 2).to eq 0.0
expect(calculator.multiply 2.0).to eq 0.0
end
it "will divide a number" do
calculator = Calculator.new
expect(calculator.divide 2).to eq 0.0
expect(calculator.divide 2.0).to eq 0.0
end
it "will make result zero on cancel" do
calculator = Calculator.new
calculator.cancel
expect(calculator.result).to eq 0.0
end
end
Have i structured the code wrongly or my test are wrong? The code works fine but not the test cases.

Well the prime culprit for the infinite loop, is the one you've introduced yourself, in CommandProcessor#parser.
I believe the problem is that you are stubbing gets on your object, but actually, it is defined on Kernel, so if you are going to stub it anywhere, that's where. This means that gets is probably returning some sort of nonsense that your parser does not understand, which leads to the infinite loop of invalid op that you see. It would probably be useful to print what the invalid op was in that case (i.e. change the puts "invalid op" to puts "invalid op: #{operation}".)
It is worth noting that even if your stub had worked, and you kept getting add 3 instead of garbage data, your programming would still loop forever, because the parser never receives an exit, which it needs to break from the loop.
In my opinion, this issue is indicative of another problem: You are trying to test two things in one go: The parser, and the IO. I would suggest altering your interface so that your CommandProcessor.parse took a string, parses it, and returns the result from the internal Calculator instance. Then it becomes almost trivial to test, which is what we want (nobody wants to think too hard).

Related

ruby - syntax error, unexpected tOP_ASGN

The following is a portion of my code. Any advice is appreciated.
def how_many_guesses?
guesses = 0
end
def guess
puts "Please type a number between 1 and a 100."
gets.chomp
end
def correct?
if guess == guess
puts "You have already guessed this number, try again!"
guess
elsif guess == random_num
you_are_correct
elsif guess > correct_num
puts "Too high!"
how_many_guesses? += 1
guess
else
puts "Too low!"
how_many_guesses? += 1
guess
end
end
The error I received was:
syntax error, unexpected tOP_ASGN
how_many_guesses? += 1
I don't understand what unexpected tOP_ASGN means.
As other commented you are trying to assign a value to a function.
You can use some other approaches for example using a class variable, or a setter method.
In the first case you use an instance variable, and you just increment or decrement its value:
class Guess
def initialize
#guesses = 0
end
def guess
puts "Please type a number between 1 and a 100."
gets.chomp
end
def correct?
if guess == guess
puts "You have already guessed this number, try again!"
guess
elsif guess == random_num
you_are_correct
elsif guess > correct_num
puts "Too high!"
#guesses += 1
guess
else
puts "Too low!"
#guesses += 1
guess
end
end
end
In this example you can use attr_reader to publish the guesses variable
The other way, more c++ style, is using getter and setter functions:
class Guess
def initialize
#guesses = 0
end
def get_guesses
#guesses
end
def set_guesses(guesses)
#guesses = guesses
end
def how_many_guesses?
guesses = 0
end
def guess
puts "Please type a number between 1 and a 100."
gets.chomp
end
def correct?
if guess == guess
puts "You have already guessed this number, try again!"
guess
elsif guess == random_num
you_are_correct
elsif guess > correct_num
puts "Too high!"
set_guesses(get_guesses + 1)
guess
else
puts "Too low!"
set_guesses(get_guesses + 1)
guess
end
end
end
This is another solution, but it is not recommended in ruby.
EDIT: Some comments included the option of using global variables, just don't!
To elaborate on #Gavriel's answer, functions are singletons, while += is an assignment operator
Your code can be rewritten to basically look like so:
def how_many_guesses?
guesses = 0
end
how_many_guesses? = how_many_guesses? + 1
Which is impossible, as you can not assign to function symbols in this manner. It is possible to redefine a function on the fly due to Ruby's robust metaprogramming capabilities, but that falls far outside the scope of this question.
The easiest way for you to fix it is to do the following:
numGuesses = how_many_guesses?
numGuesses += 1
how_many_guesses? is a function, you can only assign a value to a variable. But maybe you should read a bit more about functions generally, as your code indicates you might need some clarification.
What you probably wanted to do is something like:
$guesses = 0
and
$guesses += 1
You don't need a function here I think.
But the bigger problem will be that every time you wrote down "guess" it will wait for you to type a number.
if guess == guess # here you'll enter 2 numbers
puts "You have already guessed this number, try again!"
guess # here you'll enter another one
elsif guess == random_num # and if the 1st and 2nd numbers were different then you'll be asked another one here...
Option 1:
Replace all how_many_guesses? with $guesses making it a global variable. Define guesses at the top of the file: $guesses = 0
Option 2 (Better):
Put everything in a class and make guesses an instance variable:
class GuessGame
def initialize
#guesses = 0
end
def guess
# ...
end
def correct?
# ...
#guesses += 1
end
end

Command not found Ruby - trying to create 24 game

I am trying to solve the "24" game. The point of the game is to generate 4 random integers from 1-9 and ask the player to use addition, subtraction, multiplication, or division to get the number 24. My code runs fine until a player enters a number, and then I get "Command not found". Can someone please take a look at this:
def evaluate (input,solved_v)
input = eval (input.to_f)
#convert to a float and then evaluates it; it computes
if input == 24
solved_v = true
puts "great job! you did it!"
else
puts "please try again"
end
end
def test_entry (input)
if input.scan(%r{[^\d\s()+*/-]}).empty?
#this scan detects letters and special characters because only numbers work
true
else
false
end
end
puts
puts "try to use +, -, / or * to"
puts "get 24 from the integers provided"
puts
series = (1..4).collect{ rand(1..9)}
#generates 4 random numbers between 1 and 9
for i in series
puts i
end
puts "Please guess"
solved = false
unless solved = true
user_input = gets.chomp
if test_entry(user_input) == true
evaluate(user_input)
else
puts "invalid characters entered"
puts "please try again"
puts
end
end
There are numerous problems with your program.
Don't put spaces between your method names and parentheses.
eval takes a string argument, not a float.
Ruby passes arguments by value, so solved_v isn't going to get
returned. Make it the return value of your evaluate method. I'd also
suggest renaming your methods to express their boolean intent. See below...
Don't check boolean expressions for equality to true or false, just use them.
def correct?(input)
if eval(input) == 24
puts "great job! you did it!"
true
else
puts "please try again"
false
end
end
def good_entry?(input)
input.scan(%r{[^\d\s()+*/-]}).empty?
end
and they get used as follows
while true
user_input = gets.chomp
if good_entry?(user_input)
break if correct?(user_input)
else
...
end
end
Finally, note that you're not actually checking that the input provided by the user uses only the supplied random numbers.

Catch and throw not working in ruby

I am trying to make a number guessing game in Ruby but the program exits after I type in yes when I want to play again. I tried using the catch and throw but it would not work. Could I please get some help.
Here is my code.
class Game
def Play
catch (:start) do
$a=rand(11)
puts ($a)
until $g==$a
puts "Guess the number between 0-10."
$g=gets.to_i
if $g>$a
puts "The number you guessed is too high."
elsif $g==$a
puts "Correct you won!!!"
puts "Would you like to play again?"
$s=gets()
if $s=="yes"
$c=true
end
if $c==true
throw (:start)
end
elsif $g<$a
puts "The number you guessed is too low."
end
end
end
end
end
Game.new.Play
Edit: Here's my new code after trying suggestions:
class Game
def Play
catch (:start) do
$a=rand(11)
puts ($a)
while $s=="yes"
until $g==$a
puts "Guess the number between 0-10."
$g=gets.chomp.to_i
if $g>$a
puts "The number you guessed is too high."
elsif $g==$a
puts "Correct you won!!!"
puts "Would you like to play again?"
$s=gets.chomp
if $s=="yes"
throw (:start)
end
elsif $g<$a
puts "The number you guessed is too low."
end
end
end
end
end
end
Game.new.Play
Your first problem is here:
$s=gets()
if $s=="yes"
$c=true
end
The gets method will read the next line including the new line character '\n', and you compare it to only "yes":
> gets
=> "yes\n"
The idiomatic way to fix this in Ruby is the chomp method:
> gets.chomp
=> "yes"
That said, your code has two other deficiencies.
You may come from a language such as PHP, Perl, or even just Bash scripting, but Ruby doesn't require the dollar sign before variables. Using a $ gives a variable global scope, which is likely not what you want. In fact, you almost never want a variable to have global scope.
Ruby uses three types of symbol prefixes to indicate scope - # for instance, ## for class, and $ for global. However the most common type of variable is just local which doesn't need any prefix, and what I would suggest for your code.
I have always been told that it is very bad practice to use exceptions for control structure. Your code would be better served with a while/break structure.
When you do gets(), it retrieves the full line with a '\n' in the end. You need to trim the new line character by using:
$g=gets.chomp.to_i
Same for other gets
Based on your updated code (where you fixed the newline problem shown by others), your new problem is that you have wrapped all your game inside while $s=="true". The very first time your code is run, $s is nil (it has never been set), and so you never get to play. If you used local variables instead of global variables (s instead of $s) this would have become more obvious, because the code would not even have run.
Here's one working way that I would re-write your game.
class Game
def play
keep_playing = true
while keep_playing
answer = rand(11) # Make a new answer each time
puts answer if $DEBUG # we don't normally let the user cheat
loop do # keep going until I break from the loop
puts "Guess the number between 0-10."
guess = gets.to_i # no need for chomp here
if guess>answer
puts "The number you guessed is too high."
elsif guess<answer
puts "The number you guessed is too low."
else
puts "Correct you won!!!",
"Would you like to play again?"
keep_playing = gets.chomp.downcase=="yes"
break
end
end
end
end
end
Game.new.play
I know this doesn't really answer your question about why your code isn't working, but after seeing the code you posted I just had to refactor it. Here you go:
class Game
def initialize
#answer = rand(11)
end
def play
loop do
guess = get_guess
display_feedback guess
break if guess == #answer
end
end
def self.play_loop
loop do
Game.new.play
break unless play_again?
end
end
private
def get_guess
puts "Guess the number between 0-10."
return gets.chomp.to_i
end
def display_feedback(guess)
if guess > #answer
puts "The number you guessed is too high."
elsif guess < #answer
puts "The number you guessed is too low."
elsif guess == #answer
puts "Correct you won!!!"
end
end
def self.play_again?
puts "Would you like to play again?"
return gets.chomp == "yes"
end
end
Game.play_loop

trouble with variables in a method

In a previous question ( Is a method in ruby similar to a subroutine?) I asked about methods in Ruby. Now, writing my first ever method, I've clearly run into trouble with the scope of variables. The program below interprets and runs fine when I don't call the method learn. That is, if I remove the call learn(2) in line 33, everything works fine and it doesn't seem to matter that I use various variables (e.g. stimulus[]) both in the main program and in the method. But when I insert the call (and use it by pushing the u key), I get the message below, apparently indicating it's not alright to use stimulus in the method.
brain.rb:26:in `block in learn': undefined local variable or method `stimulus' for main:Object (NameError)
from brain.rb:25:in `each'
from brain.rb:25:in `learn'
from brain.rb:33:in `ucr'
from brain.rb:69:in `<main>'
But I NEED to use it (and brain) there, and with their present values as determined by the main program. All the answers to questions about scope that I've come across seem to go the other way, i.e, problems using variables in a method elsewhere. I thought of making stimulus and brain global, but apparently that is a no-no. How do I tell the method to use variables from the program?
Ps. Once this method works, I will be calling it from six other places in the program.
require 'matrix'
class Matrix
def []=(i, j, x)
#rows[i][j] = x
end
end #code to allow putting individual elements in matrix at i,j
def read1maybe
return $stdin.read_nonblock 1
rescue Errno::EAGAIN
return ''
end # part of code to get keypress
brain= Matrix[ [0,0,0,0,99,0,0,0,0,1,0],
[0,0,0,0,0,99,0,0,0,1,0],
[0,0,0,0,0,0,99,0,0,1,0],
[25,0,0,0,0,0,0,1,-1,1,-99],
[0,23,0,0,0,0,0,1,-1,1,1],
[0,0,24,0,0,0,0,1,-1,1,1],
[0,0,0,22,0,0,0,1,-1,1,1] ]
stimulus=Matrix.column_vector([0,0,0,0,0,0,0,0,0,0,0])
behavior=Matrix.column_vector([0,0,0,0,0,0,0])
t=500 # t=threshold
energy=50
# begin defining behavioral methods
def learn(ix)
for j in (7..10)
if stimulus[j]>0 && brain[ix,j] != 0 && brain[ix,j] < 99 then
brain[ix,j]+=int(0.1 * stimulus[j]) * (99-brain[ix,j])
end # if stim
end # for j
end # learn
def ucr
puts "Show UCR"
learn(2)
end
def positive_fixer
puts "Positive fixer"
end
def negative_fixer
puts "Negative fixer"
end
# end defining behavioral methods
# begin main program
while(energy>0) do
(0..10).each {|n| if stimulus[n,0]>2 then stimulus[n,0]+= -2 else stimulus[n,0]==0 end}
input=false
system 'stty cbreak'
look=0
while look < 40000
q = read1maybe
break if q.length > 0
look +=1
end # while look
case q
when "f" then stimulus[4,0]=9 and puts "Good!"
when "p" then stimulus[5,0]=9 and puts "Bad!"
when "u" then stimulus[6,0]=9
when "l" then stimulus[7,0]=9 and stimulus[8,0]=9 and puts "ight on"
when "c" then stimulus[9,0]=9 and puts " BUZZZ"
input=true
end # case q
system 'stty cooked'
if input==false then (0..3).each { |n| stimulus[n,0]=rand(25)} end
behavior=brain*stimulus
if behavior[0,0] > t then positive_fixer end
if behavior[1,0] > t then negative_fixer end
if behavior[2,0] > t then ucr end
if behavior [3,0] > t then puts "show operant 1" end # and stimulus[10,0]=9
if behavior[4,0] > t then puts "show operant 2" end
if behavior[5,0] > t then puts "show operant 3" end
if behavior[6,0] > t then puts "show operant 4" end
energy += -1
# temp to test development of memory
puts brain[2,9]
end # while energy > 0
puts
puts "It's dead Jim."
# end main program
stimulus and brain are declared outside of the method. You need to pass them in as parameters like so:
def learn(ix, brain, stimulus)
for j in (7..10)
if stimulus[j]>0 && brain[ix,j] != 0 && brain[ix,j] < 99 then
brain[ix,j]+=int(0.1 * stimulus[j]) * (99-brain[ix,j])
end # if stim
end # for j end # l
And edit ucr like so:
def ucr(brain, stimulus)
puts "Show UCR"
learn(2, brain, stimulus)
end
And invoke ucr like ucr(brain, stimulus). See the pattern? You need to add the parameters to the method definitions that use them and then pass them in when invoking the method.

Ruby StringScanner used for lexing : how to get the line number?

I am using StringScanner for lexical analysis like this :
def next
#scanner.skip(/\s+/)
value,kind=nil,nil
TOKEN_DEF.each{|tok,regex| (kind=tok;break) if #scanner.scan(regex)}
return Token.new(kind,value,#line,#scanner.pos)
end
At first approximation, this works well, except that I can't figure out how to now get the #line number.
I have read the doc, where begin_of_line? method seems appropriate, but I cannot figure how to use it.
Keep the text that you are scanning in a variable and use 'count'
I use the following in my code:
def current_line_number; #text[0..#scanner.pos].count("\n") + 1; end
This code doesn't seem ready to go and for sure somewhere else more elegant solution, it just should give you something to think about.
class Retry < StandardError
end
class TextScanner
def initialize(filename)
#lines = IO.readlines(filename)
#fiber = Fiber.new do
#lines.each_with_index do |line, index|
#scanner = StringScanner.new(line)
#scanner.skip(/\s+/)
value, kind = nil, nil
begin
got_token = false
TOKEN_DEF.each do |tok, regex|
if #scanner.scan(regex)
Fiber.yield Token.new(tok, value, index, #scanner.pos)
got_token = true
end
end
raise Retry if got_token
rescue Retry
retry
end
end
"fiber is finished"
end
end
def next
#fiber.resume
end
end
text_scanner = TextScanner('sometextfile')
puts text_scanner.next #=> first token
puts text_scanner.next #=> second token
puts text_scanner.next #=> third token
...
puts text_scanner.next #=> "fiber is finished"
I think I have a simple solution. Here it is :
def next
#line+=1 while #scanner.skip(/\n/)
#line+=1 if #scanner.bol?
#scanner.skip(/\s+/)
#line+=1 if #scanner.bol?
#scanner.skip(/\s+/)
return :eof if #scanner.eos?
TOKEN_DEF.each { |tok,syntax| (kind=tok;break) if #scanner.scan(syntax)}
return Token.new(kind,nil,#line,#scanner.pos)
end

Resources