Making SimpleCov work when executing gem binary - ruby

I am trying to add SimpleCov coverage to my gem that has a binary.
I would like to test its command line interface, so I wish to test the binary execution itself, and not the library it uses.
I am getting no coverage report (0 LOC) by SimpleCov.
As I understand, the issue is most likely due to the fact that in my test (either cucumber features or rspec specs), I am executing the gem's binary with system or popen3, but I don't know how I can tell SimpleCov to "follow through" (or if I am barking at the right tree...).
I have tried playing with SimpleCov.command_name, SimpleCov.pid = $$, SimpleCov.track_files and almost every other remotely related configuration I found.
I do not wish to use Aruba, although I have tried reviewing their source to look for possible answers.
Related code snippets:
# spec_helper.rb
require 'simplecov'
SimpleCov.start
require 'rubygems'
require 'bundler'
Bundler.require :default, :development
# test_spec.rb
require 'spec_helper'
describe "my bin" do
it "should be covered" do
system 'bin/runme'
end
end
I have prepared a minimal repo as an easy testing ground, if that helps at all.

well it's not easy to find a solution to this issue in particular.
not sure this solves your problem but maybe a start?
without the start block which modifies the filters i could not get SimpleCov to watch the bin directory (using the sample github repo you provided).
Used command_name to give the main processes coverage reports a name and then in the fork used command_name to give the forked processes report a name (SimpleCov merges them for us as long as they have different names).
then used load to load the bin file instead of using system.
(I couldn't figure out a way to make system or spawn add to the coverage reports, maybe if you called it through a script that restarts SimpleCov for you with an alternate command_name)
again, not sure if this is exactly what you are looking for but may be a start. code below:
# spec_helper.rb
require 'simplecov'
SimpleCov.command_name "main_report"
SimpleCov.start do
filters.clear # This will remove the :root_filter and :bundler_filter that come via simplecov's defaults
add_filter do |src|
!(src.filename =~ /^#{SimpleCov.root}/) unless src.filename =~ /bin/ #make sure the bin directory is allowed
end
end
require 'rubygems'
require 'bundler'
Bundler.require :default, :development
# test_spec.rb
require 'spec_helper'
describe "my bin" do
it "should be covered" do
pid = Process.fork do
SimpleCov.start do
command_name "bin_report_section"
end
load "bin/runme"
end
end
end
result:
Coverage report generated for bin_report_section, main_report to
/home/korreyd/simplecov-debug/coverage.
1 / 1 LOC (100.0%) covered.

Have you tried this? https://blog.yossarian.net/2018/04/01/Code-coverage-with-Simplecov-across-multiple-processes
Basically, in your spec_helper.rb
if ENV["COVERAGE"]
require "simplecov"
# Only necessary if your tests *might* take longer than the default merge
# timeout, which is 10 minutes (600s).
SimpleCov.merge_timeout(1200)
# Store our original (pre-fork) pid, so that we only call `format!`
# in our exit handler if we're in the original parent.
pid = Process.pid
SimpleCov.at_exit do
SimpleCov.result.format! if Process.pid == pid
end
# Start SimpleCov as usual.
SimpleCov.start
end
Then inside your bin/runme, add:
if ENV["COVERAGE"]
# Give our new forked process a unique command name, to prevent problems
# when merging coverage results.
SimpleCov.command_name SecureRandom.uuid
SimpleCov.start
end
Child process' test coverage will merge into the parent's process.
If you use SimpleCov's coverage_dir, make sure it's in all SimpleCov.start blocks so that results are written to same location.

Related

How to quickly test Class behavior in ruby

I'm building a class-based Tic-tac-toe game with all the classes in tic_tac_toe.rb. I can load the class into irb for interactive testing with irb -r ./tic_tac_toe.rb, but I have to manually create a player and gameboard instance every time. I included p1 = Player.new int tic_tac_toe.rb but that does not seem to run.
More generally, is what I'm doing a good workflow or not? How should I go about writing some code for my class and testing it and going back? (Is there something simpler than unit testing for this small project?)
To directly address your question, you can simplify your workflow greatly with the addition of RSpec. RSpec is a BDD (behavior driven development) tool for Ruby that will let you describe your classes in an (arguably) more descriptive way than plain jane unit tests. I have included a small code sample below to help get you started.
Create a Gemfile if you do not have one for your project and add RSpec. If you've never done this check out Bundler for more information on Gemfiles.
# in your Gemfile
gem 'rspec' # rspec testing tool
gem 'require_relative' # allows you to require files with relative paths
Create a spec folder to house your specs (specs are what RSpec calls its tests).
# via Command Line (or in Windows Explorer) create a spec folder in your project
mkdir spec
Create a spec_helper.rb in the spec/ folder to house the configuration for your tests.
# in spec/spec_helper.rb
require "rspec" # require rspec testing tool
require_relative '../tic_tac_toe' # require the class to be tested
config.before(:suite) do
begin
#=> code here will run before your entire suite
#first_player = Player.new
#second_player = Player.new
ensure
end
end
Now that you've setup two players before your test suite runs, you can use these in your tests. Create a spec for your class that you would like to test and suffix it with _spec.
# in spec/player_spec.rb
require 'spec_helper' # require our setup file and rspec will setup our suite
describe Player do
before(:each) do
# runs before each test in this describe block
end
it "should have a name" do
# either of the bottom two will verify player's name is not nil, for example
#first_player.name.nil? == false
#first_player.name.should_not be_nil
end
end
Run these tests from the root of your project by using bundle exec rspec. This will look for a spec/ folder, load the spec helper, and run your specs. There is much more you can do with RSpec, such as work in Factories etc (this would be for larger projecxts). However for your project you would only need a few specs for your classes.
Other things I would suggest would be RSpec-Given, when you have a firm grasp of rspec. This gem helps DRY up your rspec tests and makes them a bit more readable.
You can also look into Guard and creating a Guardfile, which will watch your files for you and run tests when you change files.
Lastly, I included a small suggestion on a basic project structure to visualize this a bit easier.
/your_project
--- Gemfile
--- tic_tac_toe.rb
--- spec/
------- spec_helper.rb
------- player_spec.rb
I have linked all the referenced docs so if you have any questions definitely check the links out. The documentation on Bundler, RSpec, RSpec-Given, and Guard is pretty decent. Happy programming.

SimpleCov :: Periodic Coverage Report

I have a requirement where in, i need to get the coverage so far.
If I stop the server, the report gets generated and I do get the coverage so far. But if i start the server again, my previous coverage results are lost and I can only get the coverage after the server was restarted.
Is there a way for me to keep checking periodically for the the coverage% - without stopping the server?
If i try to generate a report without starting the server, by using the following command, in rails console (SimpleCov.result.format! ),I dont get anycoverage number.
The following is my config in my config/boot.rb:
require 'simplecov'
SimpleCov.start 'rails' do
add_filter "/vendor/"
end
Please share your thoughts
Thanks
Ramya
This is the content of my boot.rb:
require 'simplecov'
# # create coverage directory if it doesn't exist already.
Dir.mkdir("coverage") unless Dir.exist?("coverage")
SimpleCov.start 'rails' do
SimpleCov.use_merging(true)
add_filter "/vendor/"
SimpleCov.merge_timeout 30
end
require 'rubygems'
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
The pre-requisites for SimpleCov to work properly are documented here: Getting Started with SimpleCov. You must be having the SimpleCov related code inside the boot.rb file after the Rails loading code. This is wrong. Promote all that code to the top and the SimpleCov.result.format! method will work inside the console.
However, it's generally a bad idea to have any extra code inside the boot.rb. Usually, coverage reports are needed only in the test environment (when the code is committed and a continuous integration server like Travis runs the full test suite and generates a coverage report). Hence, the documentation refers to this style of setup where everything related to SimpleCov runs in the test environment. The first topic in the Getting Started section mentions that you need to have the SimpleCov.start line at the beginning of the test_helper file (spec_helper.rb if you're using Rspec) since that is the file that loads the Rails environment; which means that you end up loading SimpleCov and it's configuration before loading the actual application code and you get a correct output.
require 'simplecov'
SimpleCov.start do
coverage_dir "custom-coverage_"+Time.now.strftime("%m_%d_%Y").to_s
end

How to integrate rubocop with Rake?

rubocop is a code style checker for Ruby. A similar tool to rubocop, Cane, can be integrated with Rake. I prefer rubocop to Cane since rubocop makes checks based on the Ruby Style Guide and it seems to spot more problems. To automate the process of style checking I would like to integrate rubocop with Rake so that the build fails if code quality is lacking.
Gem already supports adding tests to packages via Rake. I would like to do the same with style checks so that style checks are run along with the tests. How can I do this?
If it helps to start with a Rakefile here is one:
# -*- coding: utf-8; mode: ruby -*-
require 'bundler/gem_tasks'
require 'rake/testtask'
Rake::TestTask.new do |t|
t.libs << 'test'
t.test_files = FileList['test/unit/test*.rb']
end
desc 'Run tests'
task default: :test
As of version 0.10.0 rubocop contain a custom rake task that you can use. Just put the following in your Rakefile
require 'rubocop/rake_task'
RuboCop::RakeTask.new
Make sure to use upper-case 'R' and 'C' or you will get a NameError.
I highly recommend,
require 'rubocop/rake_task'
RuboCop::RakeTask.new(:rubocop) do |t|
t.options = ['--display-cop-names']
end
This uses the rubocop's own rake tasks and allows you to pass options if you like.
You will probably find https://github.com/yujinakayama/guard-rubocop useful if you use Guard for your RSpec tests. It enables Rubocop to give you instant feedback as soon as you save the file, along with your test results.
I needed to do something similar myself, and ended up looking in the internal source code of the RuboCop::RakeTask here:
https://github.com/rubocop/rubocop/blob/a34a1c2c2dd1fa6d90ffd06c183421a495a0717c/lib/rubocop/rake_task.rb#L40-L43
require 'rubocop'
cli = CLI.new
puts 'Running RuboCop...' if verbose
result = cli.run(options)
abort('RuboCop failed!') if result.nonzero? && fail_on_error
You can actually invoke similar code directly in your own codebase / rake task.
I ended up writing a little wrapper module I can call to, with some default flags that I always want to be applied:
module RubocopCli
def self.run!(*args)
require "rubocop"
cli = RuboCop::CLI.new
result = cli.run(["--display-cop-names", "--force-exclusion", "--fail-level", "autocorrect", *args])
raise "RubocopCli.run! Linting failed." if result.nonzero?
end
end
Then you can call it with additional args from any task, or app code, like:
files_to_lint = %w[lib/whatever.rb spec/lib/whatever_spec.rb]
RubocopCli.run!("--auto-correct", *files_to_lint)
You can shell out via Rake with the options you prefer:
desc 'Run Rubocop with options'
task rubocop: :environment do
sh 'bundle exec rubocop -D --format offenses --format progress || true'
end
I then recommend modifying the default task to include the output. The trick is to clear the task and then add back what you want. Note the need to end with || true so that an error from Rubocop will not prevent the next task from running. Here's what I do, which also uses parallel tests:
task(:default).clear.enhance ['parallel:parallel_prepare', 'parallel:spec',
'parallel:features', 'lint:rubocop',
'lint:rails_best_practices']
I would recommend shelling out to the rubocop program. It's the simplest solution. Just add this to your Rakefile:
task test: :rubocop
task :rubocop do
sh 'rubocop'
end

SimpleCov 0% coverage

I'm working on a small gem and included simplecov to spec_helper.rb two lines:
require 'simplecov'
SimpleCov.start
When I run the rspec test, the simplecov seems started correctly but the report is not:
Finished in 0.00214 seconds
8 examples, 0 failures
Coverage report generated for /home/......
spec to /home/megas/Work/calc/coverage. 0 / 0 LOC (0.0%) covered.
What might be a problem and how to fix it? Thanks
Also make sure to enable simplecov (a.k.a. SimpleCov.start) at the very beginning of your file; especially before you require your code.
I had the same symptoms. My problem was that in my test file:
#spec/oneclass_spec.rb
require 'oneclass'
require 'spec_helper'
...Rest of the test file
And I needed to change the order of the requires to:
#spec/oneclass_spec.rb
require 'spec_helper'
require 'oneclass'
...Rest of the test file
Hope this helps someone, I was going crazy...
I had a similar issue. For some reason, some modules were reported with 0% of coverage. After some investigation, I found that one of the initialisers required a controller, which caused modules to be loaded before Simplecov. What I did I moved Simplecov to initialiser:
# config/initializers/_coverage_rspec.rb
if Rails.env.test?
require 'simplecov'
SimpleCov.start 'rails'
end
If you're using spring, remember to turn it off when you run tests with coverage. In another case, the complete application code will be loaded before SimpleCov and report 0% coverage.
Just in case the above two answers didn't work (as in my case) a user on simplecov's github issues page suggested this, which worked for me.
Add this after you require simplecov-
module SimpleCov::Configuration
def clean_filters
#filters = []
end
end
SimpleCov.configure do
clean_filters
load_adapter 'test_frameworks'
end
If one of the above didn't work.
verify that in test.rb:
config.eager_load = false
In my case the issue was spring - I had to create a config/spring.rb with the following:
if ENV['RAILS_ENV'] == 'test'
require 'simplecov'
SimpleCov.start
end
as documented here.
I am running scripts from the command line and I found the solution was simply to put an exit at the end of my script. Doh!
Alternatively, the following also works
SimpleCov.at_exit do
SimpleCov.result.format!
end

Testing a rake task in rspec (and cucumber)

I'm new to Ruby, and I've been trying to learn Rake, RSpec, and Cucumber. I found some code that will help me test my Rake tasks, but I'm having trouble getting it to work. I was told here: http://blog.codahale.com/2007/12/20/rake-vs-rspec-fight/ to drop this:
def describe_rake_task(task_name, filename, &block)
require "rake"
describe "Rake task #{task_name}" do
attr_reader :task
before(:all) do
#rake = Rake::Application.new
Rake.application = #rake
load filename
#task = Rake::Task[task_name]
end
after(:all) do
Rake.application = nil
end
def invoke!
for action in task.instance_eval { #actions }
instance_eval(&action)
end
end
instance_eval(&block)
end
end
into my spec_helper.rb file.
I've managed to take this code out and run it in my cucumber steps like this:
When /^I run the update_installers task$/ do
#rake = Rake::Application.new
Rake.application = #rake
load "lib/tasks/rakefile.rb"
#task = Rake::Task["update_installers"]
for action in #task.instance_eval { #actions }
instance_eval(&action)
end
instance_eval(&block)
Rake.application = nil
end
but when I try to get things working in rspec, I get the following error.
ArgumentError in 'Rake task
install_grapevine should install to
the mygrapevine directory'
wrong number of arguments (1 for 2)
/spec/spec_helper.rb: 21:in instance_eval'
/spec/spec_helper.rb: 21:inblock in invoke!'
/spec/spec_helper.rb: 20:in each'
/spec/spec_helper.rb: 20:ininvoke!'
/spec/tasks/rakefile_spec.rb:12:in `block (2 levels) in
'
Unfortunately, I've got just under a week of ruby under by belt, so the metaprogramming stuff is over my head. Could anyone point me in the right direction?
This works for me: (Rails3/ Ruby 1.9.2)
When /^the system does it's automated tasks$/ do
require "rake"
#rake = Rake::Application.new
Rake.application = #rake
Rake.application.rake_require "tasks/cron"
Rake::Task.define_task(:environment)
#rake['cron'].invoke
end
Substitute your rake task name here and also note that your require may be "lib/tasks/cron" if you don't have the lib folder in your load path.
I agree that you should only do minimal work in the Rake task and push the rest to models for ease of testing. That being said I think it's important to ensure that the code is ACTUALLY run in my cron tasks during my integration tests so I think very mild testing of the rake tasks is justified.
Since testing rake is just too much for me, I tend to move this problem around. Whenever I find myself with a long rake task that I want to test, I create a module/class in lib/ and move all the code from the task there. This leaves the task to a single line of Ruby code, that delegates to something more testable (class, module, you name it). The only thing that remains untested is whether the rake task invokes the right line of code (and passes the right parameters), but I think that is OK.
It might be useful to tell us which is the 21nd line of your spec_helper.rb. But given that the approach you posted digs deep in rake (referring to its instance variables), I would entirely abandon it for what I suggested in the previous paragraph.
I've just spent a little while getting cucumber to run a rake task so I thought I'd share my approach. Note: This is using Ruby 2.0.0 and Rake 10.0.4, but I don't think the behaviour has changed since previous versions.
There are two parts to this. The first is easy: with a properly set up instance of Rake::Application then we can access tasks on it by calling #[] (eg rake['data:import']). Once we have a task we can run it by calling #invoke and passing in the arguments (eg rake['data:import'].invoke('path/to/my/file.csv').
The second part is more awkward: properly setting up an instance of Rake::Application to work with. Once we've done require 'rake' we have access to the Rake module. It already has an application instance, available from Rake.application, but it's not yet set up — it doesn't know about any of our rake tasks. It does, however, know where to find our Rakefile, assuming we've used one of the standard file names: rakefile, Rakefile, rakefile.rb or Rakefile.rb.
To load the rakefile we just need to call #load_rakefile on the application, but before we can do that we need to call #handle_options. The call to #handle_options populates options.rakelib with a default value. If options.rakelib is not set then the #load_rakefile method will blow up, as it expects options.rakelib to be enumerable.
Here's the helper I've ended up with:
module RakeHelper
def run_rake_task(task_name, *args)
rake_application[task_name].invoke(*args)
end
def rake_application
require 'rake'
#rake_application ||= Rake.application.tap do |app|
app.handle_options
app.load_rakefile
end
end
end
World(RakeHelper)
Pop that code into a file in features/support/ and then just use run_rake_task in your steps, eg:
When /^I import data from a CSV$/ do
run_rake_task 'data:import', 'path/to/my/file.csv'
end
The behavior might have changed since the correct answer was posted. I was experiencing problems executing two scenarios that needed to run the same rake task (only one was being executed despite me using .execute instead of .invoke). I thought to share my approach to solve the issue (Rails 4.2.5 and Ruby 2.3.0).
I tagged all the scenarios that require rake with #rake and I defined a hook to setup rake only once.
# hooks.rb
Before('#rake') do |scenario|
unless $rake
require 'rake'
Rake.application.rake_require "tasks/daily_digest"
# and require other tasks
Rake::Task.define_task(:environment)
$rake = Rake::Task
end
end
(Using a global variable is suggested here: https://github.com/cucumber/cucumber/wiki/Hooks#running-a-before-hook-only-once)
In the step definition I simply called $rake
# step definition
Then(/^the daily digest task is run$/) do
$rake['collector:daily_digest'].execute
end
Any feedback is welcome.

Resources