Buildr, RSpec—pass build parameter to test - ruby

I have »buildr« »buildfile« which triggers some »rspec« tests. I would like to pass some path parameters to the tests, so that It wont cause trouble to load test-resources files. In the »buildfile« I have got this code to trigger the tests:
RSpec.configure do |config|
config.add_setting :spec_resources_dir, :default => _(:src, 'spec', 'ruby', 'resources')
end
RSpec::Core::RakeTask.new(:run_rspec) do |t|
t.pattern = 'src/spec/**/*_spec.rb'
end
task test => [:run_rspec]
But if I try to retrieve the value in the specfile like this:
RSpec.configuration.spec_resources_dir
I get this error
undefined method `spec_resources_dir' […] (NoMethodError)
Any ideas?

RSpec's rake task runs the specs in a separate process, so configuration you do with RSpec.configure in the buildfile will not be visible to the running specs.
Two suggestions for passing info from the buildfile to your spec task:
Generate a spec_helper and require it from your specs (or via rspec's -r command line option and the rspec_opts config parameter on RSpec::Core::RakeTask). You could use buildr's filtering to substitute values from the buildfile into the helper.
Set values in ENV and then read them out from your specs. Environment variables are shared from parent to child processes.
By request, an example for #1:
RSpec::Core::RakeTask.new do |t|
t.rspec_opts = "-r '#{_(:target, 'spec_helper.rb')}'"
end
This assumes that you (probably in another task) generate the spec helper into _(:target, 'spec_helper.rb')

Related

Testing multiple hosts with the same test using serverspec

The Advanced Tips section of the Serverspec site shows an example of testing multiple hosts with the same test set. I've built an example of my own (https://gist.github.com/neilhwatson/81249ad393800a76a8ad), but there are problems.
The first problem is that the tests stop at the first failure rather than proceeding through the lot and keeping a tally. The second is that the failure output does not indicate on which host the failure occurred. What can I do to fix these problems and produce a final report for all hosts?
For the first issue, ServerSpec by default will run all your tests. However, since you have a loop that executes a Rake task for each environment, the first environment to have a failure causes the task to fails and so an exception is raised and the rest of your tasks don't run.
I've forked your gist and updated the Rake task to surround it with a begin/rescue.
...
begin
desc "Run serverspec to #{host}"
RSpec::Core::RakeTask.new(host) do |t|
ENV['TARGET_HOST'] = host
t.pattern = "spec/base,cfengine3/*_spec.rb"
end
rescue
end
...
For the second problem, it doesn't look like ServerSpec will output which environment the tests are running in. But since the updated Gist shows that the host gets set in the spec_helper.rb we can use that to add an RSpec configuration that sets up an after(:each) and only output the host on errors. The relevant code changes are in a fork of the gist, but basically you'll just need the below snippet in your spec_helper.rb:
RSpec.configure do |c|
c.after(:each) do |example|
if example.exception
puts "Failed on #{host_run_on}"
end
end
end

Is there a way to hook a before and after suite, in a Rakefile?

My current Rakefile looks like this:
# ...
task :test do
# build testing environment
RSpec::Core::RakeTask.new(:spec) do |t|
# ...
end
Rake::Task["spec"].execute
# remove testing environment
end
Unfortunately this does not execute anything after Rake::Task["spec"].execute for some reasons that I can't possibly conceive.
So, is there a way to specify something to execute before and after the test suite runs, within the Rakefile?
If your specs failed, maybe that's why nothing after Rake::Task["spec"].execute gets executed. Instead of using the Rakefile, why not use Rspec's before(:suite) and after(:suite) hooks? You can put that code in your spec_helper.rb if you have one.
https://www.relishapp.com/rspec/rspec-core/v/2-0/docs/hooks/before-and-after-hooks#before/after-blocks-defined-in-config-are-run-in-order

My Ruby install \bin\ruby.exe is not getting found when running rake_task for rspec

I can run rspec directly but with a Rakefile as simple as
require 'rspec/core/rake_task'
task :default => [:spec]
RSpec::Core::RakeTask.new do |task|
end
I get something like this output
C:\Ruby193 -S rspec ./spec/hello_spec.rb
You can see that it has not found the ruby executable.
How does rspec know where it is?
I shut off my environment variable for Ruby and it still does this.
I had to set environment variables for two types so I could still find binaries in the main Ruby install.
First for user I put the entire path to ruby\bin\ruby.exe and for system I put just the path to the bin directory. This seemed to solve my problem and I can still run other binaries.
USER:
%RUBY% = C:\Ruby193\bin\ruby.exe and append to PATH
SYSTEM:
%RUBY% = C:\Ruby193 and append this to Path as %RUBY%\bin
I use ansicon.exe plus the dll from root where ever as long as the same drive.
require 'rspec/core/rake_task'
task :default => [:spec]
RSpec::Core::RakeTask.new do |task|
task.pattern = "./spec/*_spec.rb"
task.rspec_opts = ['-c']# for ansi colors
end
Note you do not need default in the block. task is the default which is test. Just set some options with it.

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

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