I have 2 spec files that use different docker images and therefore are suppose to start separate and different docker containers to run the examples.
In the snippets below I'm using the serverspec gem to test my containers
spec/dockerfile/ember_spec.rb
require 'spec_helper'
require 'shared_examples/release'
describe 'ember' do
before(:all) do
#image = Docker::Image.build_from_dir(image_path('ember'))
set :os, family: :alpine
set :backend, :docker
set :docker_image, #image.id
set :docker_container_create_options, { 'Entrypoint' => ['/bin/sh'] }
end
describe command('ember version') do
its(:stdout) { should contain 'ember-cli: 3.3.0' }
its(:stdout) { should contain 'node: 10.10.0' }
end
include_examples 'os release', 'Alpine Linux'
end
spec/dockerfile/gerbv_spec.rb
require 'spec_helper'
require 'shared_examples/release'
describe 'gerbv' do
before(:all) do
#image = Docker::Image.build_from_dir(image_path('gerbv'))
set :os, family: :debian
set :backend, :docker
set :docker_image, #image.id
set :docker_container_create_options, { 'Entrypoint' => ['/bin/sh'] }
end
describe package('gerbv') do
it { should be_installed }
end
include_examples 'os release', 'Ubuntu 18.04'
end
However when running bundle exec rspec it is quite clear that the same container is being used to run each spec file. I have confirmed this by printing out the running containers before each of the examples. This is of course causing the specs to fail for one of the files (whichever runs second).
When the files are run independently using bundle exec rspec path/to/file then all the specs pass.
Is there any way to force a container to be spun down after the examples in one file have run and a new container created for the other set of examples?
I found a way to solve the problem, albeit a pretty hacky one. The key to this problem lay in how the container is finally released. When there are no longer any references pointing to the Docker instance it will be garbage collected and the container killed and deleted. However the object instance is held in a class level variable as a singleton in the base class. It would seem to me the only way to "reset" specinfra is to call the clear method inherited on the Docker class.
In the end the following solved the problem and the correct class is being used to run each spec.
after(:all) {
Specinfra.backend.class.clear
}
It would be great to know there is a better way to access this methods without having to rely on a method not exposed through the serverspec gem.
Related
I have specs that use variables from a shared context. The set up looks something like this:
shared_context.rb
RSpec.shared_context :handy_things do
let(:handy_object) { ... }
end
test_spec.rb:
require 'shared_context.rb'
describe 'Something happens' do
include_context :handy_things
before(:each) do
handy_object.option = true
end
...
The handy_object from the context is not recognized by Rubymine. I get an inspection message 'Cannot find...'.
This is not the end of the world but it annoys me.
Is there a way to let Rubymine know the variables of the shared context are accessible everywhere. Or as workaround to add my shared_context variables to some kind of dictionary so Rubymine doesn't flag it as unknown?
You might try two things:
Replacing your require with require_relative "shared_context" (with the relative path included if they're in different folders, no .rb extension necessary)
Specifying require "./shared_context"
I'm quite new to all of this. I am trying to test out a puppet module using Beaker. I keep getting this:
NoMethodError: undefined method `describe' for
#Beaker::TestCase:0x007fd6f95e6460
/Users/user1/beaker/Puppet/puppet-files/spec/classes/unit_spec.rb:3
/Users/user1/.rvm/gems/ruby-2.2.7/gems/beaker-3.24.0/bin/beaker:9
/Users/user1/.rvm/gems/ruby-2.2.7/bin/ruby_executable_hooks:15
/Users/user1/.rvm/gems/ruby-2.2.7/bin/ruby_executable_hooks:15.
This is the command that I'm running - "beaker --hosts myhost.yaml --pre-suite spec".
My unit_spec.rb contains this:
require 'puppetlabs_spec_helper/rake_tasks'
describe 'application' do
context 'applied to supported operating system' do
on_supported_os.each do |os, facts|
context "#{os}" do
let(:facts) do
facts
end
context "without any parameters" do
let(:params) {{ }}
it { is_expected.to compile.with_all_deps }
it { is_expected.to contain_class('files') }
end
end
end
end
context 'applied to unsupported operating system' do
describe 'ubuntu-14-x86_64' do
let(:facts) {{
:osfamily => 'Debian',
:operatingsystem => 'Ubuntu'
}}
it { is_expected.to raise_error(Puppet::Error, /Ubuntu not supported/) }
end
end
end
Any help would be much appreciated! Btw, I am using 'puppetlabs_spec_helper/rake_tasks' due to the fact that when I just used 'spec_helper' it gave me an error that it could not "load such file" even though it was there.
Also, I have tried doing
RSpec.Describe
That did not fix the issue either. I get the following error -
NameError: undefined local variable or method `on_supported_os' for #Class:0x007f92a61d5e58
I realise that this might be an RSpec Puppet issue, as this module was previously tested through puppet RSpec, however now I am trying to test using Beaker and not quite sure how to fully achieve that!
You're mixing up unit- and VM-based testing.
rspec-puppet (and rspec-puppet-facts' on_supported_os) are for catalog-based unit testing. They do not require a VM, and can give you quick feedback on syntax, and logic of your module.
beaker, and the recommended beaker-rspec add-on, provide full end-to-end testing capabilities, using actual VMs, and testing a complete stack deploy (as defined in you tests).
The main entry point for existing modules is usually rake. Take a look at the existing rake tasks in the module using rake -T. In a well-written module it should have tasks for both rspec-puppet (usually called spec), and beaker (often called beaker, or acceptance).
If it is your own module, you also might want to look into the new Puppet Development Kit to get the most important tools in a single installer.
TL;DR: I can't delete my Docker image from inside my RSpec script, because RSpec still has a dependent container
I'm using RSpec to test my Dockerfiles. That is, to test whether the Dockerfile results in a correct image. My test script does this by building an image from the Dockerfile, and then letting RSpec test whether a container (based on that image) has the correct contents.
The execution flow is as follows:
My RSpec script builds the Docker image (in its before(:all) block)
Serverspec creates a Docker container (based off the Docker image)
RSpec / Serverspec executes the tests inside the Docker container
RSpec / Serverspec calls my RSpec script's after(:all) block
RSpec / Serverspec deletes the Docker container
I tried to delete the image from inside the after(:all) block (or the after(:context) block, but that has the same issue), as that is the last hook that RSpec offers me. Only I can't, because the Docker container has not been deleted yet.
Force-deletion is no solution, as Docker then keeps an unnamed, untagged version of the image around.
Is there an (idiomatic) way to accomplish this?
Example RSpec script:
require "serverspec"
require "docker-api"
describe "Dockerfile" do
image_name = 'my_image'
image_tag = 'my_tag'
image_name_tag = image_name + ':' + image_tag
before(:all) do
set :os, family: :debian
set :backend, :docker
# Build image
#image = Docker::Image.build_from_dir("../dockerfile/")
#image.tag(repo: image_name, tag: image_tag)
# Provide Serverspec with our image's id
set :docker_image, #image.id
end
it "is a Debian-based container" do
expect(os_version).to include("Debian")
end
def os_version
command("cat /etc/*release").stdout
end
after(:context) do
# Clean up
#image.delete(name: image_name_tag) # <-- this fails
end
end
(It's not entirely clear to me where RSpec's responsibilities end and Serverspec's begin; hence the RSpec / Serverspec above.)
Still making my first steps in Ruby (while dealing with some written code). I am getting the following warning each time I run spec (listed as is):
Problem while setting context on example startundefined local variable or method `selenium_driver' for #<Spec::Example::ExampleGroup::Subclass_1::Subclass_1:0x7f2d2cd840e0>
(Edit: Split into two lines, it says)
Problem while setting context on example start
undefined local variable or method `selenium_driver' for #<Spec::Example::ExampleGroup::Subclass_1::Subclass_1:0x7f2d2cd840e0>
While grep-ing through Ruby code - could find the following:
/home/user/.rvm/gems/ruby-1.8.7-p334#frontend/gems/selenium-client-1.2.18/lib/selenium/rspec/spec_helper.rb: STDERR.puts "Problem while setting context on example start" + e
So here is the excerpt from the source code of spec_helper.rb:
config.append_before(:each) do
begin
if selenium_driver && selenium_driver.session_started?
selenium_driver.set_context "Starting example '#{self.description}'"
end
rescue Exception => e
STDERR.puts "Problem while setting context on example start" + e
end
end
Kindly advise how can I solve the (potential) problem.
Update: This grep might be helpful as well:
user#vm-ubuntu:~/dev/branch/tests$
grep selenium_driver *
my_module.rb: #selenium_driver = driver
my_module.rb: ['TERM', 'INT'].each {|s| Signal.trap(s) { #selenium_driver.stop && Process.exit(1) } }
my_module.rb: return #selenium_driver
Update N2:
My Gemfile:
source "http://rubygems.org" # Default source
gem "hpricot", "~>0.8.4"
gem "json", "~>1.5.1"
gem "rspec", "~>1.3.2"
gem "selenium-client", "~>1.2.18"
My selenium_helper.rb file:
require 'selenium/client'
require "selenium/rspec/spec_helper"
...
The problem is that selenium-client gem expects that you name your driver object 'selenium_driver' and make it visible from the spec.
For example if you initialize selenium like this:
before(:all) do
#driver = create_driver($hub_url, $hub_port, $browser)
#driver.start_new_browser_session
end
You need to change it to look like this:
attr_reader :selenium_driver
before(:all) do
#selenium_driver = create_driver($hub_url, $hub_port, $browser)
#selenium_driver.start_new_browser_session
end
Basically it's same code just different variable name. The selenium-client uses that convention to apply context information to the tests.
It's saying it can't find the variable selenium_driver.
Problem while setting context on example startundefined local variable or method `selenium_driver' for #<Spec::Example::ExampleGroup::Subclass_1::Subclass_1:0x7f2d2cd840e0>
is made up of the string "Problem while setting context on example start" plus the exception error message (what's produced by e) of
"undefined local variable or method `selenium_driver' for #<Spec::Example::ExampleGroup::Subclass_1::Subclass_1:0x7f2d2cd840e0>"`.
Add
gem "selenium-client"
to your Gemfile (don't forget to run $ bundle install)
And add following to spec/spec_helper.rb
require "selenium/client"
require "selenium/rspec/spec_helper"
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.