Single spec duration - ruby-on-rails

Is there a way in RSpec to show every single test duration and not just the total suite duration?
Now we have
Finished in 7 minutes 31 seconds (files took 4.71 seconds to load)
but I'd like to have something like
User accesses home and
he can sign up (finished in 1.30 seconds)
he can visit profile (finished in 3 seconds)
.
.
.
Finished in 7 minutes 31 seconds (files took 4.71 seconds to load)

You can use rspec --profile N, which would show you the top N slowest examples.

For a quick solution see #maximf's answer. For an alternative solution, you could write your own rspec formatter, which would give you greater control over what you are measuring.
For example, extnding rspec's base text formatter:
RSpec::Support.require_rpec_core "formatters/base_text_formatter"
module RSpec::Core::Formatters
class TimeFormatter < BaseTextFormatter
Formatters.register self, :example_started, :example_passed
attr_accessor :example_start, :longest_example, :longest_time
def initialize(output)
#longest_time = 0
super(output)
end
def example_started(example)
#example_start = Time.now
super(example)
end
def example_passed(example)
time_taken = Time.now - #example_start
output.puts "Finished #{example.example.full_description} and took #{Helpers.format_duration(time_taken)}"
if #time_taken > #longest_time
#longest_example = example
#longest_time = time_taken
end
super(example)
end
def dump_summary(summary)
super(summary)
output.puts
output.puts "The longest example was #{#longest_example.example.full_Description} at #{Helpers.format_duration(#longest_time)}"
end
end
end
Note that this will only log times on passed examples, but you could add an example_failed failed to do similar, it also only works with RSpec 3. This is based on my work on my own formatter: https://github.com/yule/dots-formatter

Instead of doing rspec --profile Neverytime we run specs (as #maximf said), we can add it to our RSpec configuration:
RSpec.configure do |config|
config.profile_examples = 10
end

Related

Rails 5 - Sidekiq worker shows job done but nothing happens

I'm using Sidekiq for delayed jobs with sidekiq-status and sidekiq-ent gems. I've created a worker which is reponsible to update minor status to false when user is adult and has minor: true. This worker should be fired every day at midnight ET. Like below:
#initializers/sidekiq.rb
config.periodic do |mgr|
# every day between midnight 0 5 * * *
mgr.register("0 5 * * *", MinorWorker)
end
#app/workers/minor_worker.rb
class MinorWorker
include Sidekiq::Worker
def perform
User.adults.where(minor: true).remove_minor_status
rescue => e
Rails.logger.error("Unable to update minor field. Exception: #{e.message} : #{e.backtrace.join('\n')}")
end
end
#models/user.rb
class User < ApplicationRecord
scope :adults, -> { where('date_of_birth <= ?', 18.years.ago) }
def self.remove_minor_status
update(minor: false)
end
end
No I want to check this on my local machine - to do so I'm using gem 'timecop' to timetravel:
#application.rb
config.time_zone = 'Eastern Time (US & Canada)'
#config/environments/development.rb
config.after_initialize do
t = Time.local(2021, 12, 21, 23, 59, 0)
Timecop.travel(t)
end
After firing up sidekiq by bundle exec sidekiq and bundle exec rails s I'm waiting a minute and I see that worker shows up:
2021-12-21T22:59:00.130Z 25711 TID-ovvzr9828 INFO: Managing 3 periodic jobs
2021-12-21T23:00:00.009Z 25711 TID-ovw69k4ao INFO: Enqueued periodic job SettlementWorker with JID ddab15264f81e0b417e7dd83 for 2021-12-22 00:00:00 +0100
2021-12-21T23:00:00.011Z 25711 TID-ovw69k4ao INFO: Enqueued periodic job MinorWorker with JID 0bcd6b76d6ee4ff9e7850b35 for 2021-12-22 00:00:00 +0100
But it didn't do anything, the user's minor status is still set to minor: true:
2.4.5 :002 > User.last.date_of_birth
=> Mon, 22 Dec 2003
2.4.5 :001 > User.last.minor
=> true
Did I miss something?
EDIT
I have to add that when I'm trying to call this worker on rails c everything works well. I've got even a RSpec test which also passes:
RSpec.describe MinorWorker, type: :worker do
subject(:perform) { described_class.new.perform }
context 'when User has minor status' do
let(:user1) { create(:user, minor: true) }
it 'removes minor status' do
expect { perform }.to change { user1.reload.minor }.from(true).to(false)
end
context 'when user is adult' do
let(:registrant2) { create(:registrant) }
it 'not change minor status' do
expect(registrant2.reload.minor).to eq(false)
end
end
end
end
Since this is the class method update won't work
def self.remove_minor_status
update(minor: false)
end
Make use of #update_all
def self.remove_minor_status
update_all(minor: false)
end
Also, I think it's best practice to have some test cases to ensure the working of the methods.
As of now you can try this method from rails console and verify if they actually work
test "update minor status" do
user = User.create(date_of_birth: 19.years.ago, minor: true)
User.adults.where(minor: true).remove_minor_status
assert_equal user.reload.minor, false
end
I think you need to either do update_all or update each record by itself, like this:
User.adults.where(minor: true).update_all(minor: false)
or
class MinorWorker
include Sidekiq::Worker
def perform
users = User.adults.where(minor: true)
users.each { |user| user.remove_minor_status }
rescue => e
Rails.logger.error("Unable to update minor field. Exception: #{e.message} : #{e.backtrace.join('\n')}")
end
end
You may also want to consider changing update to update! so it throws an error if failing to be caught by your rescue in the job:
def self.remove_minor_status
update!(minor: false)
end

How to override Date.today when running rails tests?

I have some tests that were failing on specific dates, because someone wrote them to use Date.today. I want to reproduce the failures on previous select dates.
Is there a way to run rake test with an ENV variable that will override the system clock? So that calls to Date.today, Time.now, and 1.day.ago and 1.day.from_now will use the date I set?
Like, for example:
> DATE_TODAY='2017-01-04' rake test
For testing you can use timecop gem.
It offers you two useful methods Timecop.freeze and Timecop.travel.
For example, you can use freeze to statically set time in before hooks:
describe 'your tests' do
before do
Timecop.freeze(Time.new(2017, 1, 4))
end
after do
Timecop.return
end
it 'should do something' do
sleep(10)
Time.now # => 2017-01-04 00:00:00
end
end
Or in a block:
Timecop.freeze(Time.now - 3.months) do
assert product.expired?
end
While with the travel method, you change the starting moment, but time is still passing by.
Timecop.travel(Time.new(2017, 1, 4))
sleep(10)
Time.now # => 2017-01-04 00:00:10
As of Rails 4.1 you can do
travel_to Time.new(2004, 11, 24, 01, 04, 44)
The full API docs are here: http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html

Temporize Addon's Cron Syntax

I'm trying out Heroku's Temporize Add-on and am having trouble interacting with their recurring API. Any help would be appreciated!
When I make a POST request to create a new recurring task, I receive the following message: "failed to parse either date or cron expression".
Unfortunately I get this message with every cron statement I try, even the one used in their example. To make matters worse, the API endpoint they list on their website is different than what they list in their API docs "/events/cron/url" vs "/events/when/cron/url".
Some cron statements I've tried:
0 12 * * * *
0 0 12 1/1 * ? *
15 * * * * *
The code and cron syntax I'm using is similar to what they use on their website but I'll post it here just in case. For what it's worth the "single task" endpoint works as expected. Hopefully I'm just doing something silly...
Links:
http://www.temporize.net; http://docs.temporize.apiary.io
require 'rubygems'
require 'httparty'
require 'cgi'
require 'time'
class Temporize
include HTTParty
base_uri "https://api.temporize.net/v1"
attr_accessor :credentials
def initialize
uri = URI(ENV["TEMPORIZE_URL"])
self.credentials = {:username => uri.user, :password => uri.password}
end
# Schedule a test event to run right away
def single(callback_url, date = Time.now.utc.iso8601)
url = CGI::escape(callback_url)
Temporize.post("/events/#{date}/#{url}", :basic_auth => self.credentials)
end
# Schedule a test event to run on a schedule
def recurring(callback_url, cron_schedule)
cron = CGI::escape(cron_schedule) # ex "5 10 * * ?"
url = CGI::escape(callback_url)
Temporize.post("/events/#{cron}/#{url}", :basic_auth => self.credentials)
end
end
t = Temporize.new
t.recurring("http://example.com/callback", "0 12 * * * *")
# => Result: failed to parse either date or cron expression
Per the docs, cron syntax, the syntax is:
<minutes> <hours> <day of month> <month> <day of week>
All of your examples have 6 fields, not 5. Double check to ensure you have 5 space-separated fields for your cron syntax.

How to set default pageload timeout in Watir?

I have a page that I'd like to raise error if it loads too slow.
Is there some method for Watir analogous to Watir-Webdriver's:
client = Selenium::WebDriver::Remote::Http::Default.new
client.timeout = 10
#browser = Watir::Browser.new :firefox, http_client: client
Watir-Classic does not have an API for controlling how long to wait for a page to load.
When clicking a link or using the goto method, the Browser#wait method is called. This will block execution until the page is loaded. It is hard-coded to timeout if the page does not load in 5 minutes:
def wait(no_sleep=false)
#xml_parser_doc = nil
#down_load_time = 0.0
interval = 0.05
start_load_time = ::Time.now
Timeout::timeout(5*60) do
...
end
Solution 1 - Use Timeout
If you only need to change timeout for a small number of scenarios, the simplest option may be to use the Timeout library.
For example, www.cnn.com takes 9 seconds to load on my computer. However, to only wait up to 5 seconds, you can wrap the goto (or click) method in an extra timeout:
Timeout::timeout(5) do
browser.goto 'www.cnn.com'
end
#=> execution expired (Timeout::Error)
Solution 2 - Monkey patch Browser#wait
If you want the change to apply to all pages, you could overwrite the Browser#wait method to use a different timeout. For example, overwriting it to only be 5 seconds:
require 'watir-classic'
module Watir
class Browser
def wait(no_sleep=false)
#xml_parser_doc = nil
#down_load_time = 0.0
interval = 0.05
start_load_time = ::Time.now
# The timeout can be changed here (it is in seconds)
Timeout::timeout(5) do
begin
while #ie.busy
sleep interval
end
until READYSTATES.has_value?(#ie.readyState)
sleep interval
end
until #ie.document
sleep interval
end
documents_to_wait_for = [#ie.document]
rescue WIN32OLERuntimeError # IE window must have been closed
#down_load_time = ::Time.now - start_load_time
return #down_load_time
end
while doc = documents_to_wait_for.shift
begin
until READYSTATES.has_key?(doc.readyState.to_sym)
sleep interval
end
#url_list << doc.location.href unless #url_list.include?(doc.location.href)
doc.frames.length.times do |n|
begin
documents_to_wait_for << doc.frames[n.to_s].document
rescue WIN32OLERuntimeError, NoMethodError
end
end
rescue WIN32OLERuntimeError
end
end
end
#down_load_time = ::Time.now - start_load_time
run_error_checks
sleep #pause_after_wait unless no_sleep
#down_load_time
end
end
end
browser.goto 'www.cnn.com'
#=> execution expired (Timeout::Error)
You could put the timeout value into a variable so that it can be dynamically changed.

How can Heroku:cron handle a range of hours?

I have a cron job on Heroku that needs to run for 8 hours a day. (Once an hour, eight times, each day.).
These do work as expected:
(all code in /lib/tasks/cron.rake)
if Time.now.hour == 6
puts "Ok it's 6am and I printed"
puts "6:00 PST put is done."
end
if Time.now.hour == 7
puts "Ok it's 7am and I printed"
puts "7:00 PST put is done."
end
if Time.now.hour == 8
puts "I printed at 8am"
puts "8:00 PST put is done."
end
if Time.now.hour == 9
puts "9:00 PST open put"
puts "9:00 PST put is done."
end
This code, however, doesn't work:
if Time.now.hour (6..14)
puts "I did the rake job"
end
I've also tried the following variants. Didn't work.
if Time.now.hour == (6..14)
if Time.now.hour = (6..14)
if Time.now.hour == 6..14
if Time.now.hour = 6..14
Anyone know how I can put a range in a Heroku cron job? Listing lots of jobs by hour just seems wrong.
You want to see if that range includes the hour:
if (6..14).include?(Time.now.hour)
puts "I did the rake job"
end
What you want is
if (6..14) === Time.now.hour
# Run command
end

Resources