Override rake test:units runner - ruby

I recently decided to write a simple test runtime profiler for our Rails 3.0 app's test suite. It's a very simple (read: hacky) script that adds each test's time to a global, and then outputs the result at the end of the run:
require 'test/unit/ui/console/testrunner'
module ProfilingHelper
def self.included mod
$test_times ||= []
mod.class_eval do
setup :setup_profiling
def setup_profiling
#test_start_time = Time.now
end
teardown :teardown_profiling
def teardown_profiling
#test_took_time = Time.now - #test_start_time
$test_times << [name, #test_took_time]
end
end
end
end
class ProfilingRunner < Test::Unit::UI::Console::TestRunner
def finished(elapsed_time)
super
tests = $test_times.sort{|x,y| y[1] <=> x[1]}.first(100)
output("Top 100 slowest tests:")
tests.each do |t|
output("#{t[1].round(2)}s: \t #{t[0]}")
end
end
end
Test::Unit::AutoRunner::RUNNERS[:profiling] = proc do |r|
ProfilingRunner
end
This allows me to run the suites like so rake test:xxx TESTOPTS="--runner=profiling" and get a list of Top 100 tests appended to the end of the default runner's output. It works great for test:functionals and test:integration, and even for test:units TEST='test/unit/an_example_test.rb'. But if I do not specify a test for test:units, the TESTOPTS appears to be ignored.

In classic SO style, I found the answer after articulating clearly to myself, so here it is:
When run without TEST=/test/unit/blah_test.rb, test:units TESTOPTS= needs a -- before its contents. So the solution in its entirety is simply:
rake test:units TESTOPTS='-- --runner=profiling'

Related

How to write a Ruby (Minitest) unit test that fails if I don't have the correct require statements?

I have this test:
require 'minitest/autorun'
require 'minitest/color'
require_relative '../lib/util/input_file'
class TestInputFile < Minitest::Test
def setup
#input_path = Pathname.new("/path/to/inputs")
end
def test_default_input_file
input_file = Util::InputFile.new(1)
expected_path = #input_path.join('input01.txt')
assert_equal(expected_path, input_file.abspath)
end
# more tests follow
end
for this code:
module Util
class InputFile
def initialize(num)
#num = num
#input_dir = Pathname.new("/path/to/inputs")
end
def abspath
basename = 'input'
return #input_dir.join(format('%s%02d.txt', basename, #num))
end
end
end
When I run this with rake test, everything passes as expected; however, when I call it from my actual main script, it chokes with uninitialized constant Util::InputFile::Pathname (NameError). When I add require 'pathname' at the top of lib/util/input_file.rb, everything is fine.
Why does the unit test not fail in the same way, and how can I refactor it such that it will fail unless I have the correct require statement in the production code?
EDIT: Rakefile is as follows:
require 'minitest/test_task'
Minitest::TestTask.create do |t|
t.test_globs = ['test/**/test*.rb']
end
To run tests separately use rake test:isolated instead of rake test.
https://github.com/minitest/minitest#rake-tasks-
It seems like one of your other tests gets Pathname loaded.
How does your Rakefile look and doesn't it get pathname required? It can explain why your test (started with rake test) goes well.
And why don't you want to require pathname at the top of lib/util/input_file.rb?

How to make sure each Minitest unit test is fast enough?

I have a large amount of Minitest unit tests (methods), over 300. They all take some time, from a few milliseconds to a few seconds. Some of them hang up, sporadically. I can't understand which one and when.
I want to apply Timeout to each of them, to make sure anyone fails if it takes longer than, say, 5 seconds. Is it achievable?
For example:
class FooTest < Minitest::Test
def test_calculates_something
# Something potentially too slow
end
end
You can use the Minitest PLugin loader to load a plugin. This is, by far, the cleanest solution. The plugin system is not very well documented, though.
Luckily, Adam Sanderson wrote an article on the plugin system.
The best news is that this article explains the plugin system by writing a sample plugin that reports slow tests. Try out minitest-snail, it is probably almost what you want.
With a little modification we can use the Reporter to mark a test as failed if it is too slow, like so (untested):
File minitest/snail_reporter.rb:
module Minitest
class SnailReporter < Reporter
attr_reader :max_duration
def self.options
#default_options ||= {
:max_duration => 2
}
end
def self.enable!(options = {})
#enabled = true
self.options.merge!(options)
end
def self.enabled?
#enabled ||= false
end
def initialize(io = STDOUT, options = self.class.options)
super
#max_duration = options.fetch(:max_duration)
end
def record result
#passed = result.time < max_duration
slow_tests << result if !#passed
end
def passed?
#passed
end
def report
return if slow_tests.empty?
slow_tests.sort_by!{|r| -r.time}
io.puts
io.puts "#{slow_tests.length} slow tests."
slow_tests.each_with_index do |result, i|
io.puts "%3d) %s: %.2f s" % [i+1, result.location, result.time]
end
end
end
end
File minitest/snail_plugin.rb:
require_relative './snail_reporter'
module Minitest
def self.plugin_snail_options(opts, options)
opts.on "--max-duration TIME", "Report tests that take longer than TIME seconds." do |max_duration|
SnailReporter.enable! :max_duration => max_duration.to_f
end
end
def self.plugin_snail_init(options)
if SnailReporter.enabled?
io = options[:io]
Minitest.reporter.reporters << SnailReporter.new(io)
end
end
end

Set timeout to ruby Test::unit run

I am using a rake task to run tests written in Ruby.
The rake task:
desc "This Run Tests on my ruby app"
Rake::TestTask.new do |t|
t.libs << File.dirname(__FILE__)
t.test_files = FileList['test*.rb']
t.verbose = true
end
I would like to create a timeout so that if any test (or the entire suite) hangs a timeout exception will be thrown and the test will fail.
I tried to create a new task that would run the test task with a timeout:
desc "Run Tests with timeout"
task :run_tests do
Timeout::timeout(200) do
Rake::Task['test'].invoke
end
end
The result was that a timeout was thrown, but the test continued to run.
I've been looking for something similar, and ended up writing this:
require 'timeout'
# Provides an individual timeout for every test.
#
# In general tests should run for less then 1s so 5s is quite a generous timeout.
#
# Timeouts can be overridden per-class (in rare cases where tests should take more time)
# by setting for example `self.test_timeout = 10 #s`
module TestTimeoutHelper
def self.included(base)
class << base
attr_accessor :test_timeout
end
base.test_timeout = 5 # s
end
# This overrides the default minitest behaviour of measuring time, with extra timeout :)
# This helps out with: (a) keeping tests fast :) (b) detecting infinite loops
#
# In general however benchmarks test should be used instead.
# Timeout is very unreliable by the way but in general works.
def time_it
t0 = Minitest.clock_time
Timeout.timeout(self.class.test_timeout, Timeout::Error, 'Test took to long (infinite loop?)') do
yield
end
ensure
self.time = Minitest.clock_time - t0
end
end
This module should be included into either specific test cases, or a general test case.
It works with a MiniTest 5.x
This code adds a timeout for entire suite:
def self.suite
mysuite = super
def mysuite.run(*args)
Timeout::timeout(600) do
super
end
end
mysuite
end

Run cleanup step if any it block failed

When one of my it blocks fails, I want to run a cleanup step. When all of the it blocks succeed I don't want to run the cleanup step.
RSpec.describe 'my describe' do
it 'first it' do
logic_that_might_fail
end
it 'second it' do
logic_that_might_fail
end
after(:all) do
cleanup_logic if ONE_OF_THE_ITS_FAILED
end
end
How do I implement ONE_OF_THE_ITS_FAILED?
Not sure if RSpec provides something out of the box, but this would work:
RSpec.describe 'my describe' do
before(:all) do
#exceptions = []
end
after(:each) do |example|
#exceptions << example.exception
end
after(:all) do |a|
cleanup_logic if #exceptions.any?
end
# ...
end
I digged a little into the RSpec Code and found a way to monkey patch the RSpec Reporter class. Put this into your spec_helper.rb:
class RSpecHook
class << self
attr_accessor :hooked
end
def example_failed(example)
# Code goes here
end
end
module FailureDetection
def register_listener(listener, *notifications)
super
return if ::RSpecHook.hooked
#listeners[:example_failed] << ::RSpecHook.new
::RSpecHook.hooked = true
end
end
RSpec::Core::Reporter.prepend FailureDetection
Of course it gets a little more complex if you wish to execute different callbacks depending on the spec you're running at the moment.
Anyway, this way you do not have to mess up your testing code with exceptions or counters to detect failures.

In RSpec, how to determine the time each spec file takes to run?

Background: My project's continuous integration build runs RSpec in several parallel runs. Specs are partitioned across parallel runs by spec file. That means long spec files dominate test suite run time. So I want to know the time each spec file takes to run (not just the time each example takes to run).
How can I get RSpec to tell me the time each spec file takes to run? Several of RSpec's stock formatters tell me the time each example takes to run, but they don't sum the time for each spec.
I'm using RSpec 3.2.
I addressed this need by writing my own RSpec formatter. Put the following class in spec/support, make sure it's required, and run rspec like so:
rspec --format SpecTimeFormatter --out spec-times.txt
class SpecTimeFormatter < RSpec::Core::Formatters::BaseFormatter
RSpec::Core::Formatters.register self, :example_started, :stop
def initialize(output)
#output = output
#times = []
end
def example_started(notification)
current_spec = notification.example.file_path
if current_spec != #current_spec
if #current_spec_start_time
save_current_spec_time
end
#current_spec = current_spec
#current_spec_start_time = Time.now
end
end
def stop(_notification)
save_current_spec_time
#times.
sort_by { |_spec, time| -time }.
each { |spec, time| #output << "#{'%4d' % time} seconds #{spec}\n" }
end
private
def save_current_spec_time
#times << [#current_spec, (Time.now - #current_spec_start_time).to_i]
end
end

Resources