Get current Step form Scenario outline - ruby

I am trying to access the current step name in for a scenario outline test. The code below works for regular scenarios but fails for scenario outlines.
AfterStep do |scenario|
#step = scenario.steps.find { |s| s.status == :skipped }
#puts step.keyword + step.name
case scenario
when Cucumber::Ast::Scenario
step = scenario.steps.find { |s| s.status == :skipped }
puts step.keyword + ' ' + step.name
#Works correctly
when Cucumber::Ast::OutlineTable::ExampleRow
# **!!!!Exception below!!!**
step = scenario.scenario_outline.steps.find { |s| s.status == :skipped }
puts step.keyword + ' ' + step.name
end
end
Exception:
NoMethodError: undefined method `steps' for #<Cucumber::Ast::OutlineTable::ExampleRow:0x007fe0b8214bc0>
Does anyone know how to resolve this? Or if this is possible?

Try the following way:
Before do |scenario|
...
# step counter
#step_count = 0
end
AfterStep do |step|
current_feature = if scenario.respond_to?('scenario_outline')
# execute the following code only for scenarios outline (starting from the second example)
scenario.scenario_outline.feature
else
# execute the following code only for a scenario and a scenario outline (the first example only)
scenario.feature
end
# call method 'steps' and select the current step
# we use .send because the method 'steps' is private for scenario outline
step_title = current_feature.feature_elements[0].send(:steps).to_a[#step_count].name
p step_title
# increase step counter
#step_count += 1
end
It has to work fine for both 'scenario' and 'scenario outline'

This did not work for me. I had to make some minor changes:
step_title = step.steps.to_a[#step_count].gherkin_statement.name

Updates circa 10/2018
Before do |scenario|
# the scenario in after step is Cucumber::Core::Test::Result::Passed and does not have the data
#scenario = scenario
# step counter
#step_count = 0
end
AfterStep do
feature = #scenario.feature # works for outlines too
if feature.feature_elements[0].respond_to?(:steps)
step_title = feature.feature_elements[0].send(:steps).to_a[#step_count].to_s
else
step_title = feature.feature_elements[0].send(:raw_steps).to_a[#step_count].to_s
end
puts "[finished step] #{step_title}"
# increase step counter
#step_count += 1
end

Related

Reset a counter in Ruby

I have the following code to compile jobs from github jobs API. How do I reset a counter back to 0 every time I call on a new city? I've tried putting it in several different places with no luck.
def ft_count_and_percentage
##url += #city
uri = URI(##url)
response = Net::HTTP.get(uri)
result = JSON.parse(response)
result.each do |job|
if job["type"] == "Full Time"
##fulltime_count += 1
end
end
puts "Total number of jobs in #{#city}: #{result.length}"
if ##fulltime_count > 0
puts ("full time percent ") + "#{(##fulltime_count/result.length) * 100}"
else
puts "No FT Positions"
end
end
##fulltime_count is defined outside this method to start at 0. Currently, as expected the counter just keeps adding jobs every time I add a new city.
boston = Job.new("Boston")
boston.ft_count_and_percentage
sf = Job.new("San Francisco")
sf.ft_count_and_percentage
la = Job.new("Los Angeles")
la.ft_count_and_percentage
denver = Job.new("Denver")
denver.ft_count_and_percentage
boulder = Job.new("Boulder")
boulder.ft_count_and_percentage
chicago = Job.new("Chicago")
chicago.ft_count_and_percentage
ny = Job.new("New York City")
ny.ft_count_and_percentage
You may need to reset it inside Job init
class Job
def initialize
##count = 0
end
def ft_count_and_percentage
#the blah you already have
end
end

formal argument cannot be a class variable in ruby

I've never used Ruby before, and am attempting to run a program written
long ago. I've installed Ruby 2.4.1 and the gem package [test-unit
3.4.3] OK, but when I try to run it, I get an error:
tcreporter.rb:156: formal argument cannot be a class variable
##tc_array.each do |##tc|
^
Is there something in particular I'm doing wrong?
Below is the code snippet :
class TCReporter
##m = nil; ##c = nil; ##tc = nil; ##status = 'p'; ##notes = nil; ##tlArr = []
##resultMapping = {'p'=>'PASS', 'f'=>'FAIL', 'b'=>'BLOCKED', 's'=>'SKIP','pr'=>'PREQFAIL', 'con'=>'CONERR', 'h'=>'HWSKIP', 'cor'=>'CORE'}
def self.report_result(currentTest, status, notes=nil)
if $trInit
##m = currentTest.split('(')[0] ###m is the test METHOD currently being executed in the framework.
##c = currentTest.split('(')[1].split(')')[0] ###c is the test CLASS currently being executed in the framework
if ##c =~ /(?:#{TESTRAIL_TEST_PREFIXES.join('|')})_\d*$/i #If there's a mapping on the test class then report a test class result.
##tc = ##c.scan(/(?:#{TESTRAIL_TEST_PREFIXES.join('|')})_\d*$/i)[0].upcase.sub('_', '-') #Get the TR class mapping
#When reporting at the test class level, the status always starts out as 'p' (PASS). If there's any
#non-passing status for any test method within the test class (blocked or failed) then use that result
#for reporting. Once the global status '##status' has been updated once then no more updating occurs.
if ##status == 'p' && status != 'p'
##status = status
##notes = notes
end
if eval("#{##c}.public_instance_methods.grep(/^test_/).sort.first") == ##m && eval("#{##c}.public_instance_methods.grep(/^test_/).sort.last") == ##m #The first test method is the last test method. All done, do a TestLink update.
begin
result = TR.report_tc_result(##tc, TESTRAIL_PROJECT, TESTRAIL_MILESTONE, TESTRAIL_PLAN, TESTRAIL_RUN, TESTRAIL_BUILD, ##status, (##notes ? ##notes : notes))
ensure
result.case_id = ##tc
result.class = ##c
result.method = ##m
if !result.success #success means a successful communication with testLink and test case was found and updated.
$trReport = ReportFile.new('tr_report.txt') if !$trReport
$trReport.puts "#{##tc}, #{TEST_ARGS.project}, #{TEST_ARGS.plan}, #{TEST_ARGS.build}, #{TEST_ARGS.platform}, #{##c}, #{##m}, #{status} 'class', #{result.message ||= result.exception}"
end
end
elsif eval("#{##c}.public_instance_methods.grep(/^test_/).sort.first") == ##m #A new test class is being evaluated. Set everything to default except status (use whatever the first test class returned).
##m = nil; ##c = nil; ##tc = nil; ##status = status
elsif eval("#{##c}.public_instance_methods.grep(/^test_/).sort.last") == ##m #Done with the test class. Time to report the test result.
begin
result = TR.report_tc_result(##tc, TESTRAIL_PROJECT, TESTRAIL_MILESTONE, TESTRAIL_PLAN, TESTRAIL_RUN, TESTRAIL_BUILD, ##status, (##notes ? ##notes : notes))
ensure
result.case_id = ##tc
result.class = ##c
result.method = ##m
if !result.success #success means a successful communication with testLink and test case was found and updated.
$trReport = ReportFile.new('tr_report.txt') if !$trReport
$trReport.puts "#{##tc}, #{TEST_ARGS.project}, #{"#{TEST_ARGS.milestone}, " if TEST_ARGS.milestone}#{TEST_ARGS.plan}, #{TEST_ARGS.build}, #{TEST_ARGS.platform}, #{##c}, #{##m}, #{status} 'class', #{result.message ||= result.exception}"
end
end
else #The test class is still being executed. Don't update TestLink yet, just check for a non-passing result.
if ##status == 'p' && status != 'p' #Update the test status if it's a non-pass result. Otherwise, use the earlier failed or blocked status.
##status = status
end
end
end
#If there's a mapping on a test method then report a test method result.
if ##m =~ /(?:#{TESTRAIL_TEST_PREFIXES.join('|')})_\d?[\d_]+$/i
##tc_array = ##m.scan(/(?:#{TESTRAIL_TEST_PREFIXES.join('|')})_\d?[\d_]+$/i)[0].upcase.sub('_', '-').split("_")
if ##tc_array.size > 1
tmp_prefix = ##tc_array[0].split("-").first
tmp_array = []
##tc_array.each do|tmp|
tmp_array << tmp_prefix + "-" + tmp.split("-").last
end
##tc_array = tmp_array
end
##tc_array.each do |##tc|
begin
result = TR.report_tc_result(##tc, TESTRAIL_PROJECT, TESTRAIL_MILESTONE, TESTRAIL_PLAN, TESTRAIL_RUN, TESTRAIL_BUILD, status, notes)
puts status
rescue => e
puts e
ensure
if result && !result.success
$trReport = ReportFile.new('tr_report.txt') if !$trReport
$trReport.puts "#{##tc}, #{TEST_ARGS.project}, #{"#{TEST_ARGS.milestone}, " if TEST_ARGS.milestone}#{TEST_ARGS.plan}, #{TEST_ARGS.build}, #{TEST_ARGS.platform}, #{##c}, #{##m}, #{status} 'class', #{result.message ||= result.exception}"
end
end
end
end
end
end
Thanks in advance
This is fixed now after making "tc" as local variable.

Two version of the same code not giving the same result

I am trying to implement a simple timeout class that handles timeouts of different requests.
Here is the first version:
class MyTimer
def handleTimeout mHash, k
while mHash[k] > 0 do
mHash[k] -=1
sleep 1
puts "#{k} : #{mHash[k]}"
end
end
end
MAX = 3
timeout = Hash.new
timeout[1] = 41
timeout[2] = 5
timeout[3] = 14
t1 = MyTimer.new
t2 = MyTimer.new
t3 = MyTimer.new
first = Thread.new do
t1.handleTimeout(timeout,1)
end
second = Thread.new do
t2.handleTimeout(timeout,2)
end
third = Thread.new do
t3.handleTimeout(timeout,3)
end
first.join
second.join
third.join
This seems to work fine. All the timeouts work independently of each other.
Screenshot attached
The second version of the code however produces different results:
class MyTimer
def handleTimeout mHash, k
while mHash[k] > 0 do
mHash[k] -=1
sleep 1
puts "#{k} : #{mHash[k]}"
end
end
end
MAX = 3
timeout = Hash.new
timers = Array.new(MAX+1)
threads = Array.new(MAX+1)
for i in 0..MAX do
timeout[i] = rand(40)
# To see timeout value
puts "#{i} : #{timeout[i]}"
end
sleep 1
for i in 0..MAX do
timers[i] = MyTimer.new
threads[i] = Thread.new do
timers[i].handleTimeout( timeout, i)
end
end
for i in 0..MAX do
threads[i].join
end
Screenshot attached
Why is this happening?
How can I implement this functionality using arrays?
Is there a better way to implement the same functionality?
In the loop in which you are creating threads by using Thread.new, the variable i is shared between main thread (where threads are getting created) and in the threads created. So, the value of i seen by handleTimeout is not consistent and you get different results.
You can validate this by adding a debug statement in your method:
#...
def handleTimeout mHash, k
puts "Handle timeout called for #{mHash} and #{k}"
#...
end
#...
To fix the issue, you need to use code like below. Here parameters are passed to Thread.new and subsequently accessed using block variables.
for i in 0..MAX do
timers[i] = MyTimer.new
threads[i] = Thread.new(timeout, i) do |a, b|
timers[i].handleTimeout(a, b)
end
end
More on this issue is described in When do you need to pass arguments to Thread.new? and this article.

Calling multiple methods on a CSV object

I have constructed an Event Manager class that performs parsing actions on a CSV file, and produces html letters using erb. It is part of a jumpstart labs tutorial
The program works fine, but I am unable to call multiple methods on an object without the earlier methods interfering with the later methods. As a result, I have opted to create multiple objects to call instance methods on, which seems like a clunky inelegant solution. Is there a better way to do this, where I can create a single new object and call methods on it?
Like so:
eventmg = EventManager.new("event_attendees.csv")
eventmg.print_valid_phone_numbers
eventmg_2 = EventManager.new("event_attendees.csv")
eventmg_2.print_zipcodes
eventmg_3 = EventManager.new("event_attendees.csv")
eventmg_3.time_targeter
eventmg_4 = EventManager.new("event_attendees.csv")
eventmg_4.day_of_week
eventmg_5 = EventManager.new("event_attendees.csv")
eventmg_5.create_thank_you_letters
The complete code is as follows
require 'csv'
require 'sunlight/congress'
require 'erb'
class EventManager
INVALID_PHONE_NUMBER = "0000000000"
Sunlight::Congress.api_key = "e179a6973728c4dd3fb1204283aaccb5"
def initialize(file_name, list_selections = [])
puts "EventManager Initialized."
#file = CSV.open(file_name, {:headers => true,
:header_converters => :symbol} )
#list_selections = list_selections
end
def clean_zipcode(zipcode)
zipcode.to_s.rjust(5,"0")[0..4]
end
def print_zipcodes
puts "Valid Participant Zipcodes"
#file.each do |line|
zipcode = clean_zipcode(line[:zipcode])
puts zipcode
end
end
def clean_phone(phone_number)
converted = phone_number.scan(/\d/).join('').split('')
if converted.count == 10
phone_number
elsif phone_number.to_s.length < 10
INVALID_PHONE_NUMBER
elsif phone_number.to_s.length == 11 && converted[0] == 1
phone_number.shift
phone_number.join('')
elsif phone_number.to_s.length == 11 && converted[0] != 1
INVALID_PHONE_NUMBER
else
phone_number.to_s.length > 11
INVALID_PHONE_NUMBER
end
end
def print_valid_phone_numbers
puts "Valid Participant Phone Numbers"
#file.each do |line|
clean_number = clean_phone(line[:homephone])
puts clean_number
end
end
def time_targeter
busy_times = Array.new(24) {0}
#file.each do |line|
registration = line[:regdate]
prepped_time = DateTime.strptime(registration, "%m/%d/%Y %H:%M")
prepped_time = prepped_time.hour.to_i
# inserts filtered hour into the array 'list_selections'
#list_selections << prepped_time
end
# tallies number of registrations for each hour
i = 0
while i < #list_selections.count
busy_times[#list_selections[i]] += 1
i+=1
end
# delivers a result showing the hour and the number of registrations
puts "Number of Registered Participants by Hour:"
busy_times.each_with_index {|counter, hours| puts "#{hours}\t#{counter}"}
end
def day_of_week
busy_day = Array.new(7) {0}
d_of_w = ["Monday:", "Tuesday:", "Wednesday:", "Thursday:", "Friday:", "Saturday:", "Sunday:"]
#file.each do |line|
registration = line[:regdate]
# you have to reformat date because of parser format
prepped_date = Date.strptime(registration, "%m/%d/%y")
prepped_date = prepped_date.wday
# adds filtered day of week into array 'list selections'
#list_selections << prepped_date
end
i = 0
while i < #list_selections.count
# i is minus one since days of week begin at '1' and arrays begin at '0'
busy_day[#list_selections[i-1]] += 1
i+=1
end
#busy_day.each_with_index {|counter, day| puts "#{day}\t#{counter}"}
prepared = d_of_w.zip(busy_day)
puts "Number of Registered Participants by Day of Week"
prepared.each{|date| puts date.join(" ")}
end
def legislators_by_zipcode(zipcode)
Sunlight::Congress::Legislator.by_zipcode(zipcode)
end
def save_thank_you_letters(id,form_letter)
Dir.mkdir("output") unless Dir.exists?("output")
filename = "output/thanks_#{id}.html"
File.open(filename,'w') do |file|
file.puts form_letter
end
end
def create_thank_you_letters
puts "Thank You Letters Available in Output Folder"
template_letter = File.read "form_letter.erb"
erb_template = ERB.new template_letter
#file.each do |line|
id = line[0]
name = line[:first_name]
zipcode = clean_zipcode(line[:zipcode])
legislators = legislators_by_zipcode(zipcode)
form_letter = erb_template.result(binding)
save_thank_you_letters(id,form_letter)
end
end
end
The reason you're experiencing this problem is because when you apply each to the result of CSV.open you're moving the file pointer each time. When you get to the end of the file with one of your methods, there is nothing for anyone else to read.
An alternative is to read the contents of the file into an instance variable at initialization with readlines. You'll get an array of arrays which you can operate on with each just as easily.
"Is there a better way to do this, where I can create a single new object and call methods on it?"
Probably. If your methods are interfering with one another, it means you're changing state within the manager, instead of working on local variables.
Sometimes, it's the right thing to do (e.g. Array#<<); sometimes not (e.g. Fixnum#+)... Seeing your method names, it probably isn't.
Nail the offenders down and adjust the code accordingly. (I only scanned your code, but those Array#<< calls on an instance variable, in particular, look fishy.)

Nest output of recursive function

I have written this piece of code, which outputs a list of jobdescriptions (in Danish). It works fine, however I would like to alter the output a bit. The function is recursive because the jobs are nested, however the output does not show the nesting.
How do I configure the function to show an output like this:
Job 1
- Job 1.1
- Job 1.2
-- Job 1.2.1
And so on...
require 'nokogiri'
require 'open-uri'
def crawl(url)
basePath = 'http://www.ug.dk'
doc = Nokogiri::HTML(open(basePath + url))
doc.css('.maplist li').each do |listitem|
listitem.css('.txt').each do |txt|
puts txt.content
end
listitem.css('a[href]').each do |link|
crawl(link['href'])
end
end
end
crawl('/Job.aspx')
require 'nokogiri'
require 'open-uri'
def crawl(url, nesting_level = 0)
basePath = 'http://www.ug.dk'
doc = Nokogiri::HTML(open(basePath + url))
doc.css('.maplist li').each do |listitem|
listitem.css('.txt').each do |txt|
puts " " * nesting_level + txt.content
end
listitem.css('a[href]').each do |link|
crawl(link['href'], nesting_level + 1)
end
end
end
crawl('/Job.aspx')
I see two options:
Pass an additional argument to the recursive function to indicate the level you are currently in. Initialize the value to 0 and each time you call the function increment this value. Something like this:
def crawl(url, level)
basePath = 'http://www.ug.dk'
doc = Nokogiri::HTML(open(basePath + url))
doc.css('.maplist li').each do |listitem|
listitem.css('.txt').each do |txt|
puts txt.content
end
listitem.css('a[href]').each do |link|
crawl(link['href'], level + 1)
end
end
end
Make use of the caller array that holds the callstack. Use the size of this array to indicate the level in the recursion you are located in.
def try
puts caller.inspect
end
try
I would personally stick to the fist version as it seems easier to read, but requires you to modify the interface.

Resources