how to troubleshoot Object.const_get failing in rake but not in console? - ruby

I have these lines of code that I use as a Factory to create objects:
class ServiceProcessor
def self.create(service, logger)
classified_name = service.name.to_s.split('_').collect! { |w| w }.join << "Processor"
logger.warn "Creating service proc, classified name: #{classified_name}"
service_proc = Object.const_get(classified_name).new
... check respond_to etc.
return service_proc
The first line might be odd, it needs to be refactored. I can use these lines to create various Processors:
class AlphaProcessor < ServiceProcessor
class BetaProcessor < ServiceProcessor
etc...
So these processors can be created in my specs and via the console. They can be created in rake as well -- but only some of them. Two of them are failing:
WARN 2012-01-27 08:54:18 -0800 (25626) Creating service proc,
classified name: AlphaProcessor
ERROR 2012-01-27 08:54:18 -0800 (25626)
Failed for service #<Service _id: 4f203c171d41c83b3b000003, _type: nil,
deleted_at: nil, name: "Alpha", enabled: true>
ERROR 2012-01-27 08:54:18
-0800 (25626) Exception: uninitialized constant AlphaProcessor ERROR
2012-01-27 08:54:18 -0800 (25626) Backtrace:
["/mnt/hgfs/kodiak/lib/service_processors/_service_processor.rb:33:in
`const_get'",
"/mnt/hgfs/kodiak/lib/service_processors/_service_processor.rb:33:in
`create'", "/mnt/hgfs/kodiak/lib/update_engine.rb:28:in `block in
update_all'",
So the question is, how should I go about figuring out why these two (out of 9) would fail, but only in Rake? I can see that Rake and the Console are loading the same environment (a few puts in the environment.rb), so I doubt that's it. I'm stumped on what could be causing this or where to look.

Have you tried adding a Kernel#require to the top of your rake file to load the proper classes? Also, how are you defining the rake task? I've been able to do this for pulling in my class constants:
task :my_task => :environment do
...
end
Also, check your filenames. Make sure that a class named "AlphaBravoTangoProcessor" is in a file named 'alpha_bravo_tango_processor.rb', not a file named 'alphabravotango_processor.rb'.

Related

chefspec: setting global node attributes to be used in all tests in recipe

I'm currently writing a chefspec recipe which sets certain node attributes, which are necessary to complete my unit tests. I am currently setting these attributes in each test, which seems wasteful. I wish to carry this out in such a way that I am not repeating code, i.e. "global attributes?".
My current working recipe is as follows:
# encoding: UTF-8
require_relative '../spec_helper'
osd_device = '/ceph-1-osd'
describe 'ceph::per-host-osd' do
let(:runner) { ChefSpec::Runner.new }
let(:chef_run) { runner.converge(described_recipe) }
let(:node) { runner.node }
# Test whether directory is created with specifed attributes
# Node attributes are necessary to satisfy logic of parent recipe
it 'creates a directory with root ownership and permissions' do
node.automatic['fqdn'] = 'ceph-1'
node.set['ceph']['hosts']['ceph-1']
node.set['ceph']['hosts']['ceph-1']['osd_devices'] = [{device: "/ceph-1-osd", journal: "/ceph-1-journal/journal", type: "directory"}]
expect(chef_run).to create_directory("#{osd_device}").with(
user: 'root',
group: 'root',
)
end
it 'executes ceph-disk-prepare and ceph-disk-activate' do
node.automatic['fqdn'] = 'ceph-1'
node.set['ceph']['hosts']['ceph-1']
node.set['ceph']['hosts']['ceph-1']['osd_devices'] = [{device: "/ceph-1-osd", journal: "/ceph-1-journal/journal", type: "directory"}]
expect(chef_run).to run_execute("ceph-disk-prepare on #{osd_device}")
expect(chef_run).to run_execute("ceph-disk-activate #{osd_device}")
end
end
This chefspec test passes without issue:
.....
Finished in 4.99 seconds (files took 8.13 seconds to load)
5 examples, 0 failures
Coverage report generated for RSpec to /Users/joe.bloggs/workspace/cookbook_ceph/build/report/coverage/coverage.xml
However, I wish to set the 'node.automatic' and 'node.set' statements only once (outside of the tests) and then to reuse them in the subsequent tests.
My efforts to set these attributes "globally" looks like this:
# encoding: UTF-8
require_relative '../spec_helper'
osd_device = '/ceph-1-osd'
describe 'ceph::per-host-osd' do
let(:chef_run) do
ChefSpec::Runner.new do |node|
node.automatic['fqdn'] = 'ceph-1'
node.set['ceph']['hosts']['ceph-1']
node.set['ceph']['hosts']['ceph-1']['osd_devices'] = [{device: "/ceph-1-osd", journal: "/ceph-1-journal/journal", type: "directory"}]
end
end
# Test whether directory is created with specifed attributes
# Node attributes are necessary to satisfy logic of parent recipe
it 'creates a directory with root ownership and permissions' do
expect(chef_run).to create_directory("#{osd_device}").with(
user: 'root',
group: 'root',
)
end
it 'executes ceph-disk-prepare and ceph-disk-activate' do
expect(chef_run).to run_execute("ceph-disk-prepare on #{osd_device}")
expect(chef_run).to run_execute("ceph-disk-activate #{osd_device}")
end
end
It returns the following error:
...FF
Failures:
1) ceph::per-host-osd creates a directory with root ownership and permissions
Failure/Error: expect(chef_run).to create_directory("#{osd_device}").with(
NoMethodError:
undefined method `resource_collection' for nil:NilClass
# ./spec/unit/per-host-osd_spec.rb:17:in `block (2 levels) in <top (required)>'
2) ceph::per-host-osd executes ceph-disk-prepare and ceph-disk-activate
Failure/Error: expect(chef_run).to run_execute("ceph-disk-prepare on #{osd_device}")
NoMethodError:
undefined method `resource_collection' for nil:NilClass
# ./spec/unit/per-host-osd_spec.rb:23:in `block (2 levels) in <top (required)>'
Finished in 3.12 seconds (files took 8.46 seconds to load)
5 examples, 2 failures
Failed examples:
rspec ./spec/unit/per-host-osd_spec.rb:16 # ceph::per-host-osd creates a directory with root ownership and permissions
rspec ./spec/unit/per-host-osd_spec.rb:22 # ceph::per-host-osd executes ceph-disk-prepare and ceph-disk-activate
Coverage report generated for RSpec to /Users/joe.bloggs/workspace/cookbook_ceph/build/report/coverage/coverage.xml
I'm new to chefspec, so perhaps I'm missing something. Any help is greatly appreciated. Thanks.
In your second form you're never converging any recipe, so obviously there's no resource_collection to test against.
Add a converge(described_recipe) at the end of your runner definition.
let(:chef_run) do
ChefSpec::Runner.new do |node|
node.automatic['fqdn'] = 'ceph-1'
node.set['ceph']['hosts']['ceph-1']
node.set['ceph']['hosts']['ceph-1']['osd_devices'] = [{device: "/ceph-1-osd", journal: "/ceph-1-journal/journal", type: "directory"}]
end.converge(described_recipe)
end

Why is my test double not expecting the command I allowed?

I have some code which makes shellout calls to the Linux OS, which will run distro-specific commands. I'm trying to ensure the tests can be run on any system, so am using a test double for the Mixlib::ShellOut call. Here's a simplified version that replicates my issue:
require 'mixlib/shellout'
class SelinuxCommand
def run
runner = Mixlib::ShellOut.new('getenforce')
runner.run_command
end
end
My test stubs Mixlib:ShellOut.new returning a test double, and then says that :run_command should return the string 'Enforcing':
require 'rspec'
require_relative 'selinuxcommand'
describe SelinuxCommand do
it 'gets the Selinux enforcing level' do
command = SelinuxCommand.new
Mixlib::ShellOut.stub(:new).and_return(double)
allow(double).to receive(:run_command).and_return('Enforcing')
expect command.run.to eql 'Enforcing'
end
end
However, when I run the test I see:
$ rspec -fd selinuxcommand_spec.rb
SelinuxCommand gets the Selinux enforcing level (FAILED - 1)
Failures:
1) SelinuxCommand gets the Selinux enforcing level
Failure/Error: expect command.run.to eql 'Enforcing'
Double received unexpected message :run_command with (no args)
# ./selinuxcommand.rb:5:in `run'
# ./selinuxcommand_spec.rb:9:in `block (2 levels) in <top (required)>'
Finished in 0.00197 seconds 1 example, 1 failure
Failed examples:
rspec ./selinuxcommand_spec.rb:5 # SelinuxCommand gets the Selinux enforcing level
I don't understand why the double doesn't expect :run_command when I explicitly set it up to expect that. What have I missed?
It is just because every time you call double you get a different object, so the object allowed to receive the run_command method is not the same object returned by the stubbed new. You can fix it like this:
it 'Gets the Selinux enforcing level' do
runner = double
Mixlib::ShellOut.stub(:new).and_return(runner)
expect(runner).to receive(:run_command).and_return('Enforcing')
expect(subject.run).to eq('Enforcing')
end
Can't check now but it seems to me that you need stub :initialize method - not :new.
Try this variant:
Mixlib::ShellOut.stub(:initialize).and_return(double)

Rspec any_instance.stub raises undefined method `any_instance_recorder_for' for nil:NilClass exception

Here is the class that I'm testing contained in Foo.rb:
class Foo
def bar
return 2
end
end
Here is the my test contained in Foo_spec.rb:
require "./Foo.rb"
describe "Foo" do
before(:all) do
puts "#{Foo == nil}"
Foo.any_instance.stub(:bar).and_return(1)
end
it "should pass this" do
f = Foo.new
f.bar.should eq 1
end
end
I am getting the following output:
false
F
Failures:
1) Foo Should pass this
Failure/Error: Foo.any_instance.stub(:bar).and_return(1)
NoMethodError:
undefined method `any_instance_recorder_for' for nil:NilClass
# ./Foo_spec.rb:6:in `block (2 levels) in <top (required)>'
Finished in 0 seconds
1 example, 1 failure
Failed examples:
rspec ./Foo_spec.rb:9 # Foo Should pass this
I've consulted the doc and the example given is passing on my machine (so it isn't a problem with the rspec code), but it isn't giving me any information on what I might be doing wrong. The error message is also quite confusing as it's telling me not to call .any_instance on a nil:NilClass, but as I proved with my output, Foo isn't nil. How am I supposed to call .any_instance.stub on my custom object?
I'm using Ruby 1.9.3 and rspec 2.14.5.
You should use before(:each) for stubbing.
Stubs in before(:all) are not supported. The reason is that all stubs and mocks get cleared out after each example, so any stub that is set in before(:all) would work in the first example that happens to run in that group, but not for any others.
rspec-mocks readme
From Rspec 3 any_instance is not defined anymore.
Now use:
allow_any_instance_of(Foo).to receive(:bar).and_return(1)
Source for this and older versions:
https://makandracards.com/makandra/2561-stub-methods-on-any-instance-of-a-class-in-rspec-1-and-rspec-2
Updating rspec worked for me. You can do it using the following command:
bundle update rspec

why is before :save callback hook not getting called from FactoryGirl.create()?

This simple example uses DataMapper's before :save callback (aka hook) to increment callback_count. callback_count is initialized to 0 and should be set to 1 by the callback.
This callback is invoked when the TestObject is created via:
TestObject.create()
but the callback is skipped when created by FactoryGirl via:
FactoryGirl.create(:test_object)
Any idea why? [Note: I'm running ruby 1.9.3, factory_girl 4.2.0, data_mapper 1.2.0]
Full details follow...
The DataMapper model
# file: models/test_model.rb
class TestModel
include DataMapper::Resource
property :id, Serial
property :callback_count, Integer, :default => 0
before :save do
self.callback_count += 1
end
end
The FactoryGirl declaration
# file: spec/factories.rb
FactoryGirl.define do
factory :test_model do
end
end
The RSpec tests
# file: spec/models/test_model_spec.rb
require 'spec_helper'
describe "TestModel Model" do
it 'calls before :save using TestModel.create' do
test_model = TestModel.create
test_model.callback_count.should == 1
end
it 'fails to call before :save using FactoryGirl.create' do
test_model = FactoryGirl.create(:test_model)
test_model.callback_count.should == 1
end
end
The test results
Failures:
1) TestModel Model fails to call before :save using FactoryGirl.create
Failure/Error: test_model.callback_count.should == 1
expected: 1
got: 0 (using ==)
# ./spec/models/test_model_spec.rb:10:in `block (2 levels) in <top (required)>'
Finished in 0.00534 seconds
2 examples, 1 failure
At least for factory_girl 4.2 (don't know since which version it is supported), there is another workwaround through the use of custom methods to persist objects. As it is stated in a response to an issue about it in Github, it is just a matter of calling save instead of save!.
FactoryGirl.define do
to_create do |instance|
if !instance.save
raise "Save failed for #{instance.class}"
end
end
end
Of course it is not ideal because it should be functional in FactoryGirl core, but I think right now it is the best solution and, at the moment, I'm not having conflicts with other tests...
The caveat is that you have to define it in each factory (but for me it wasn't an inconvenient)
Solved.
#Jim Stewart pointed me to this FactoryGirl issue where it says "FactoryGirl calls save! on the instance [that it creates]". In the world of DataMapper, save! expressly does not run the callbacks -- this explains the behavior that I'm seeing. (But it doesn't explain why it works for #enthrops!)
That same link offers some workarounds specifically for DataMapper and I'll probably go with one of them. Still, it would be nice if an un-modified FactoryGirl played nice with DataMapper.
update
Here's the code suggested by Joshua Clayton of thoughtbot. I added it to my spec/factories.rb file and test_model_spec.rb now passes without error. Cool beans.
# file: factories.rb
class CreateForDataMapper
def initialize
#default_strategy = FactoryGirl::Strategy::Create.new
end
delegate :association, to: :#default_strategy
def result(evaluation)
evaluation.singleton_class.send :define_method, :create do |instance|
instance.save ||
raise(instance.errors.send(:errors).map{|attr,errors| "- #{attr}: #{errors}" }.join("\n"))
end
#default_strategy.result(evaluation)
end
end
FactoryGirl.register_strategy(:create, CreateForDataMapper)
update 2
Well. perhaps I spoke too soon. Adding the CreateForDataMapper fixes that one specific test, but appears to break others. So I'm un-answering my question for now. Someone else have a good solution?
Use build to build your object, then call save manually...
t = build(:test_model)
t.save

Build extension spree e-commerce

I'm with one problem when executed the step 5.7. In my extension not metting the path:
#lib/spree/flag_promotion_configuration.rb.
I must create the directory 'spree' and after create the file 'flag_promotion_configuration.rb' ?
If I write this in the file, as the tutorial required:
#lib/spree_flag_promotions/engine.rb
module Spree::ActiveShipping; end
....
module SpreeFlagPromotions
class Engine < Rails::Engine
initializer "spree.flag_promotions.preferences", :after => "spree.environment" do |app|
Spree::FlagPromotions::Config = Spree::FlagPromotionConfiguration.new
end
....
end
end
And I executed:
$ rake db:migrate
Return this error:
rake aborted!
uninitialized constant Spree::FlagPromotions
Tasks: TOP => db:migrate => environment
(See full trace by running task with --trace)
I believe it has anything to do with:
#lib/spree/flag_promotion_configuration.rb.
Because they do not know where to create this file is or where.

Resources