Code not actually asserting in RSpec? - ruby

I'm new to Ruby and in various open source software I've noticed a number of "statements" in some RSpec descriptions that appear not to accomplish what they intended, like they wanted to make an assertion, but didn't. Are these coding errors or is there some RSpec or Ruby magic I'm missing? (Likelihood of weirdly overloaded operators?)
The examples, with #??? added to the suspect lines:
(rubinius/spec/ruby/core/array/permutation_spec.rb)
it "returns no permutations when the given length has no permutations" do
#numbers.permutation(9).entries.size == 0 #???
#numbers.permutation(9) { |n| #yielded << n }
#yielded.should == []
end
(discourse/spec/models/topic_link_spec.rb)
it 'works' do
# ensure other_topic has a post
post
url = "http://#{test_uri.host}/t/#{other_topic.slug}/#{other_topic.id}"
topic.posts.create(user: user, raw: 'initial post')
linked_post = topic.posts.create(user: user, raw: "Link to another topic: #{url}")
TopicLink.extract_from(linked_post)
link = topic.topic_links.first
expect(link).to be_present
expect(link).to be_internal
expect(link.url).to eq(url)
expect(link.domain).to eq(test_uri.host)
link.link_topic_id == other_topic.id #???
expect(link).not_to be_reflection
...
(chef/spec/unit/chef_fs/parallelizer.rb)
context "With :ordered => false (unordered output)" do
it "An empty input produces an empty output" do
parallelize([], :ordered => false) do
sleep 10
end.to_a == [] #???
expect(elapsed_time).to be < 0.1
end
(bosh/spec/external/aws_bootstrap_spec.rb)
it "configures ELBs" do
load_balancer = elb.load_balancers.detect { |lb| lb.name == "cfrouter" }
expect(load_balancer).not_to be_nil
expect(load_balancer.subnets.sort {|s1, s2| s1.id <=> s2.id }).to eq([cf_elb1_subnet, cf_elb2_subnet].sort {|s1, s2| s1.id <=> s2.id })
expect(load_balancer.security_groups.map(&:name)).to eq(["web"])
config = Bosh::AwsCliPlugin::AwsConfig.new(aws_configuration_template)
hosted_zone = route53.hosted_zones.detect { |zone| zone.name == "#{config.vpc_generated_domain}." }
record_set = hosted_zone.resource_record_sets["\\052.#{config.vpc_generated_domain}.", 'CNAME'] # E.g. "*.midway.cf-app.com."
expect(record_set).not_to be_nil
record_set.resource_records.first[:value] == load_balancer.dns_name #???
expect(record_set.ttl).to eq(60)
end

I don't think there is any special behavior. I think you've found errors in the test code.

This doesn't work because there's no assertion, only a comparison:
#numbers.permutation(9).entries.size == 0
It would need to be written as:
#numbers.permutation(9).entries.size.should == 0
Or using the newer RSpec syntax:
expect(#numbers.permutation(9).entries.size).to eq(0)

Related

When I add a byebug statement to my code the bug I was investigating disappears

I have run across a strange bug I'm not sure how to solve. I am trying to make a program that solves Wordle in the browser using Ruby and Selenium. I noticed that starting with the second guess, the program would just keep guessing the same word instead of incorporating feedback and fetching a new word. So I added a byebug statement to see what was going on, and then it started working. If I remove the byebug statement, the bug appears again. I couldn't believe it so I pushed to Github and cloned on another computer and the same thing happened again. Basically, as long as I put a byebug statement anywhere after line 24, the code works as expected. Here is the code.
require 'webdrivers'
require './words.rb'
require 'byebug'
class WordleSolver
attr_reader :driver
def initialize
options = Selenium::WebDriver::Options.chrome
#driver = Selenium::WebDriver.for :chrome, options: options
#driver.manage.timeouts.implicit_wait = 10
#driver.get 'https://www.nytimes.com/games/wordle/index.html'
#game_div = driver.find_element(css: 'game-app').shadow_root.find_element(css: 'div#game')
#words = PossibleWords::WORDS
#word = ['', '', '', '', '']
#present = {}
#absent = []
#current_row = 1
end
def solve
close_modal
guess('store')
5.times do
get_feedback
break if game_won?
next_guess = formulate_guess
guess(next_guess)
sleep(3)
end
end
def game_won?
#word.each { |letter| return false if letter == '' }
true
end
def close_modal
#game_div.find_element(css: 'game-modal').shadow_root.find_element(css: 'div.close-icon').click
end
def guess(word)
#driver.action.send_keys(word).send_keys(:enter).perform
end
def formulate_guess
#words.each do |word|
return word if valid_guess?(word)
end
'guess'
end
def valid_guess?(word)
#word.each_with_index do |letter, index|
if letter != ''
return false if word[index] != letter
end
end
#absent.each do |letter|
return false if word.include?(letter)
end
#present.each do |letter, incorrect_positions|
return false if !word.include?(letter)
incorrect_positions.each do |position|
return false if word[position] == letter
end
end
end
def get_feedback
row = #game_div.find_element(css: "game-row:nth-of-type(#{#current_row})").shadow_root
1.upto(5) do |n|
tile = row.find_element(css: "game-tile:nth-of-type(#{n})")
letter = tile.attribute("letter")
evaluation = tile.attribute("evaluation")
add_feedback(letter, evaluation, n - 1)
end
#current_row += 1
end
def add_feedback(letter, evaluation, position)
case evaluation
when 'absent'
#absent.push(letter)
when 'present'
if #present[letter]
#present[letter].push(position)
else
#present[letter] = [position]
end
when 'correct'
#present.delete(letter)
#word[position] = letter if #word[position] == ''
end
end
end
begin
solver = WordleSolver.new
solver.solve
sleep(5)
ensure
solver.driver.quit
end
TLDR when I add a byebug statement to my code the bug I was investigating disappears
The comment by BroiSatse solved my issue. By adding a byebug statement I was also causing the program to wait, giving it time to synchronize with the browser, which fixed the bug.

RSpec failing under unknown (seemingly specific) circumstances

I'm relatively new to ruby, moving into it from languages like Python, and C# so please forgive me if this is an obvious bug I'm missing.
The Issue
I was writing code for a tool to help myself and other developers generate .editorconfig files as a way to learn Ruby. I use rspec as my testing framework and wrote the following tests:
module EditorConfigGenerator
RSpec.describe EditorConfigGenerator::FileGenerator do
configs_sensible_defaults = [EditorConfigGenerator::EditorConfig.new({root: true, indent_style: 'space', indent_size: 2,
end_of_line: 'lf', charset: 'utf-8',
trim_trailing_whitespace: true, insert_final_newline: true})]
file_generator = EditorConfigGenerator::FileGenerator.new(configs_sensible_defaults)
context "A collection of one EditorConfig object is provided" do
it "displays a preview of the file output" do
# Newlines are automatically inserted into ruby multi-line strings
output = "root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
"
# String.delete(' ') to remove any potential formatting discrepancies
# which don't affect the test of the content.
expect(file_generator.preview_output.delete(' ')).to eq output.delete(' ')
end
it "creates a .editorconfig file in the correct location" do
dot_editorconfig = file_generator.generate_config_file('test_output/.editorconfig')
expect(dot_editorconfig.class).to eq File
expect(File.exist? "test_output/.editorconfig").to eq true
end
end
context "A collection of multiple EditorConfig objects is provided" do
it "displays a preview of the file output" do
configs = configs_sensible_defaults.push(EditorConfigGenerator::EditorConfig.new({file_type: '*.{js,py}', indent_size: 4}))
file_generator = EditorConfigGenerator::FileGenerator.new(configs)
output = "root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{js,py}]
indent_size = 4"
expect(file_generator.preview_output.delete(' ')).to eq output.delete(' ')
end
end
end
end
When I ran these tests the first few times, they worked seemingly fine. I then enabled the following recommended lines in my spec_helper.rb file
config.profile_examples = 10
config.order = :random
kernel.srand config.seed
I ran the tests a few more times, all of them passing until - seemingly randomly - I obtained the following failure output with seed 31166:
1) EditorConfigGenerator::FileGenerator A collection of one EditorConfig object is provided displays a preview of the file output
Failure/Error: expect(file_generator.preview_output.delete(' ')).to eq output.delete(' ')
expected: "root=true\n[*]\nindent_style=space\nindent_size=2\nend_of_line=lf\ncharset=utf-8\ntrim_trailing_whitespace=true\ninsert_final_newline=true\n"
got: "root=true\n[*]\nindent_style=space\nindent_size=2\nend_of_line=lf\ncharset=utf-8\ntrim_trailing_whitespace=true\ninsert_final_newline=true\n[*.{js,py}]\nindent_size=4"
(compared using ==)
Diff:
## -6,4 +6,6 ##
charset=utf-8
trim_trailing_whitespace=true
insert_final_newline=true
+[*.{js,py}]
+indent_size=4
# ./spec/file_generator_spec.rb:23:in `block (3 levels) in <module:EditorConfigGenerator>'
With other seeds I tried, it worked, but with seed 31166 it failed.
The Fix
The output made me feel like it was contextual, so therefore I tried to look for a bug in my implementation. I didn't find one, so I thought it may have been an issue with the way I defined the shared variable configs_sensible_defaults in the spec.
I decided to change the code to use before(:each) to assign an instance variable before each test (i.e. resetting the data properly) and it seemed to fix it.
Here's the fixed spec/file_generator_spec.rb
module EditorConfigGenerator
RSpec.describe EditorConfigGenerator::FileGenerator do
before(:each) do
#configs_sensible_defaults = [EditorConfigGenerator::EditorConfig.new({root: true, indent_style: 'space', indent_size: 2, end_of_line: 'lf', charset: 'utf-8', trim_trailing_whitespace: true, insert_final_newline: true})]
#file_generator = EditorConfigGenerator::FileGenerator.new(#configs_sensible_defaults)
end
context "A collection of one EditorConfig object is provided" do
it "displays a preview of the file output" do
# Newlines are automatically inserted into ruby multi-line strings
output = "root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
"
# String.delete(' ') to remove any potential formatting discrepancies
# which don't affect the test of the content.
expect(#file_generator.preview_output.delete(' ')).to eq output.delete(' ')
end
it "creates a .editorconfig file in the correct location" do
dot_editorconfig = #file_generator.generate_config_file('test_output/.editorconfig')
expect(dot_editorconfig.class).to eq File
expect(File.exist? "test_output/.editorconfig").to eq true
end
end
context "A collection of multiple EditorConfig objects is provided" do
it "displays a preview of the file output" do
configs = #configs_sensible_defaults.clone.push(EditorConfigGenerator::EditorConfig.new({file_type: '*.{js,py}', indent_size: 4}))
#file_generator = EditorConfigGenerator::FileGenerator.new(configs)
output = "root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{js,py}]
indent_size = 4"
expect(#file_generator.preview_output.delete(' ')).to eq output.delete(' ')
end
end
end
end
Here is a diff output of both files (view on Github):
RSpec.describe EditorConfigGenerator::FileGenerator do
configs_sensible_defaults = [EditorConfigGenerator::EditorConfig.new({root: true, indent_style: 'space', indent_size: 2,
end_of_line: 'lf', charset: 'utf-8',
trim_trailing_whitespace: true, insert_final_newline: true})]
file_generator = EditorConfigGenerator::FileGenerator.new(configs_sensible_defaults)
before(:each) do
#configs_sensible_defaults = [EditorConfigGenerator::EditorConfig.new({root: true, indent_style: 'space', indent_size: 2, end_of_line: 'lf', charset: 'utf-8', trim_trailing_whitespace: true, insert_final_newline: true})]
#file_generator = EditorConfigGenerator::FileGenerator.new(#configs_sensible_defaults)
end
context "A collection of one EditorConfig object is provided" do
it "displays a preview of the file output" do
# file_generator_spec.rb:22 # module EditorConfigGenerator
"
# String.delete(' ') to remove any potential formatting discrepancies
# which don't affect the test of the content.
expect(file_generator.preview_output.delete(' ')).to eq output.delete(' ')
expect(#file_generator.preview_output.delete(' ')).to eq output.delete(' ')
end
it "creates a .editorconfig file in the correct location" do
dot_editorconfig = file_generator.generate_config_file('test_output/.editorconfig')
dot_editorconfig = #file_generator.generate_config_file('test_output/.editorconfig')
expect(dot_editorconfig.class).to eq File
expect(File.exist? "test_output/.editorconfig").to eq true
end
end
context "A collection of multiple EditorConfig objects is provided" do
Finally, for context, here are the relevant file_generator.rb and editor_config.rb files
module EditorConfigGenerator
class FileGenerator
def initialize(configs)
#configs = configs
end
def preview_output
output = ""
#configs.each do |config|
if output.include? "root="
output << config.to_s_without_root
next
end
output << config.to_s
end
return output.rstrip if #configs.size > 1
output
end
def generate_config_file(location='.editorconfig')
File.delete(location) if File.exist? location
file = File.new(location, "w")
file.print(preview_output)
file.close
file
end
end
end
module EditorConfigGenerator
class EditorConfig
attr_reader :root, :indent_style, :indent_size,
:end_of_line, :charset, :trim_trailing_whitespace,
:insert_final_newline, :file_type
def initialize(options)
#root = nil
#file_type = :*
transform_options(options)
set_options(options)
end
def set_options(options)
#root = options[:root] unless options[:root].nil?
#indent_style = options[:indent_style] unless options[:indent_style].nil?
#indent_size = options[:indent_size] unless options[:indent_size].nil?
#end_of_line = options[:end_of_line] unless options[:end_of_line].nil?
#charset = options[:charset] unless options[:charset].nil?
#trim_trailing_whitespace = options[:trim_trailing_whitespace] unless options[:trim_trailing_whitespace].nil?
#insert_final_newline = options[:insert_final_newline] unless options[:insert_final_newline].nil?
#file_type = options[:file_type] unless options[:file_type].nil?
end
def transform_options(options)
options[:root] = true if options[:root] == 'y'
options[:root] = false if options[:root] == 'n'
options[:trim_trailing_whitespace] = true if options[:trim_trailing_whitespace] == 'y'
options[:trim_trailing_whitespace] = false if options[:trim_trailing_whitespace] == 'n'
options[:insert_final_newline] = true if options[:insert_final_newline] == 'y'
options[:insert_final_newline] = false if options[:insert_final_newline] == 'n'
end
def to_s
config_string = ""
config_string << "root = #{#root.to_s}\n" unless #root.nil?
config_string << "[#{#file_type}]\n"
config_string << "indent_style = #{#indent_style}\n" unless #indent_style.nil?
config_string << "indent_size = #{#indent_size}\n" unless #indent_size.nil?
config_string << "end_of_line = #{#end_of_line}\n" unless #end_of_line.nil?
config_string << "charset = #{#charset}\n" unless #charset.nil?
config_string << "trim_trailing_whitespace = #{#trim_trailing_whitespace.to_s}\n" unless #trim_trailing_whitespace.nil?
config_string << "insert_final_newline = #{#insert_final_newline.to_s}\n" unless #insert_final_newline.nil?
config_string
end
def to_s_without_root
lines = to_s.lines
lines.delete_at 0
lines.join
end
def to_s_without_line(line_identifier)
lines = to_s.lines
index = lines.index(line_identifier)
lines.delete_at index
lines.join
end
end
end
The Question
Could someone explain exactly why the change fixed the issue? Did the change fix the issue?
I believe it's to do with the collection in configs_sensible_defaults being mutated and not being reset correctly after each run, which would explain why a certain seed would trigger a failure (as maybe under that seed test 2 would run before 1).
The reason it was working sometimes and not other times is because one of your tests (in the second context) reassigned file_generator. The test in the first context was using the file_generator from the shared outer scope. So long as that test ran first, it worked as expected. Then the second test in the second scope ran, reassigned file_generator, and also passed.
Since the tests ran in random order, whenever they ran in the order they are in the file, everything was passing. When the second test ran first, it overwrote file_generator, the second test still passed, but the first test ran with the overwritten value.
You can use a before block to configure things for each test, as you did, but you should only configure common, shared values in a before block. Anything that's going to be different for each context or each test should be initialized closer to the scope in which it is used.
In your examples above, I wouldn't use any before block, at least not in the outer scope. Both #config_sensible_defaults and #file_generator are different in each context, and should not be shared at all. If you want to group a bunch of tests together in a context with the same defaults and generator, you can put a before block inside the context block, to initialize things correctly for each context.

Waitr and Jira integration not producing desired output

I wrote a Ruby script to check if the layer found in DOM in Firebug for the page www.jira.com is matching with the hash values declared in my script. Below is the Ruby script I have written:
require 'watir'
browser = Watir::Browser.new(:chrome)
browser.goto('https://jira.com')
JIRA_DATA_LAYER = {
'jira' => {
'event' => ['gtm.js', 'gtm.load'],
'gtm.start' => '1468949036556',
}
}
def get_jira_data_layer(get_data_layer)
result = []
get_data_layer.each do |data_layer|
data_layer.each do |data_layer_key, data_layer_value|
result << {"#{data_layer_key}" => data_layer_value}
end
end
return result
end
def compare_jira_data_layer(layer, jira_name)
message = []
index = 0
JIRA_DATA_LAYER[jira_name].each do |jira_key, jira_value|
if layer.include?({jira_key => jira_value})
result = 'matches - PASS'
else
result = 'matches - FAIL'
end
index += 1
message.push("'#{jira_key} => #{jira_value}' #{result}")
end
return message.join("\n")
end
data_layer = browser.execute_script("return dataLayer")
get_data_layer = get_jira_data_layer(data_layer)
compare_data_layer = compare_jira_data_layer(get_data_layer, "jira")
puts compare_data_layer
I am getting the following output:
'event => ["gtm.js", "gtm.load"]' matches - FAIL
'gtm.start => 1468949036556' matches - FAIL
I want the following to be achieved:
'event => gtm.js' matches - FAIL
'gtm.start => 1468949036556' matches - FAIL
You could simply change the value for event key in JIRA_DATA_LAYER, but I guess it has to be that way.
Try to expand if sentence when checking key for this hash and use is_a? method to check whether value for particular key is array or not. If so, loop through each member of this array.

Dynamically check if a field in JSON is nil without using eval

Here's an extract of the code that I am using:
def retrieve(user_token, quote_id, check="quotes")
end_time = Time.now + 15
match = false
until Time.now > end_time || match
#response = http_request.get(quote_get_url(quote_id, user_token))
eval("match = !JSON.parse(#response.body)#{field(check)}.nil?")
end
match.eql?(false) ? nil : #response
end
private
def field (check)
hash = {"quotes" => '["quotes"][0]',
"transaction-items" => '["quotes"][0]["links"]["transactionItems"]'
}
hash[check]
end
I was informed that using eval in this manner is not good practice. Could anyone suggest a better way of dynamically checking the existence of a JSON node (field?). I want this to do:
psudo: match = !JSON.parse(#response.body) + dynamic-path + .nil?
Store paths as arrays of path elements (['quotes', 0]). With a little helper function you'll be able to avoid eval. It is, indeed, completely inappropriate here.
Something along these lines:
class Hash
def deep_get(path)
path.reduce(self) do |memo, path_element|
return unless memo
memo[path_element]
end
end
end
path = ['quotes', 0]
hash = JSON.parse(response.body)
match = !hash.deep_get(path).nil?

Refactor ruby code to split array into two small arrays

#open = Array.new
#close = Array.new
#posts.each do |post|
if !post.status.nil? and post.status == 'Open'
#open.push(post)
else
#close.push(post)
end
end
Can i write it in less verbose way ?
Sounds like a job for partition:
partition { |obj| block } → [ true_array, false_array ]
partition → an_enumerator
Returns two arrays, the first containing the elements of enum for which the block evaluates to true, the second containing the rest.
This should do the job:
#open, #closed = #posts.partition { |p| p.status == 'Open' }
Another idea:
#open = #post.select{ |post| post.status == 'Open'}
#close = #post.reject{ |post| post.status == 'Open'}
You dont have to check nil explicity. Something like this will do.
#posts.each { |post| post.status == 'Open' ? #open.push post : #close.push }
Just to help you write code that's more Ruby-like, here's how I'd write your original code:
open_ary = []
close_ary = []
#posts.each do |post|
if post.status && post.status == 'Open'
open_ary << post
else
close_ary << post
end
end
It could be written more concisely, and would remove a couple lines, but it'd lose readability, which is always something to consider. That said, #muistooshort's answer is really the best way to do it because it relies on the partition method which was created for this sort of use-case.

Resources