Search code examples
rubyrspecserverspec

Is it possible to include common-tests in serverspec?


I'm using serverspec to carry out remote testing of servers.

I've a number of different tests, which all work fine:

`-- spec
    |-- builder.example.org.uk
       \ host_spec.rb
    |-- chat.example.org.uk
       \ host_spec.rb
    |-- docker.example.org.uk
       \ host_spec.rb
    \-- git.example.org.uk
       \ host_spec.rb

However each host-test has a lot of duplication, because I want to ensure that each host has sshd running, for example.

I've tried several different ways of creating spec/common_tests.rb but each time fails. For example adding spec/common.rb:

 describe command("lsb_release -d") do
    its(:stdout) { should match /wheezy/ }
 end

Then in spec/chat.example.org.uk/host_spec.rb:

 require 'common'

However this seems to suddenly want to connect to a different host, and fail:

shelob ~ $ bundle exec rake spec:ssh.example.org.uk 
/usr/bin/ruby1.9.1 -S rspec spec/ssh.example.org.uk/host_spec.rb
F.....................

Failures:

   1) Command "lsb_release -d" stdout
      On host `ssh.example.org.uk`
      Failure/Error: Unable to find matching line from backtrace
      SocketError: getaddrinfo: Name or service not known

So my question is twofold:

  • Is it possible to include common tests from an external file?
  • If so how do I accomplish this?

Solution

  • I'm not sure whether your example has a typo, as it seems to do exactly what you want. You're running bundle exec rake spec:ssh.example.org.uk and it's running against ssh.example.org.uk.

    The serverspec documentation suggests another way to run shared specs. Instead of organising your files by host, you should organise them by role. For instance:

    `-- spec
        |-- app
        |   `-- ruby_spec.rb
        |-- base
        |   `-- users_and_groups_spec.rb
        |-- db
        |   `-- mysql_spec.rb
        |-- proxy
        |   `-- nginx_spec.rb
        `-- spec_helper.rb
    

    Then, in your Rakefile, you map your hosts to roles:

    hosts = [{name: 'www.example.org.uk', roles: %w(base app)},
             {name: 'db.example.org.uk', roles: %w(base db)}]
    

    You can then provide a ServerSpecTask that runs commands by setting the host address as an environment variable, by overriding RSpec's spec_command method:

    class ServerspecTask < RSpec::Core::RakeTask
      attr_accessor :target
    
      def spec_command
        cmd = super
        "env TARGET_HOST=#{target} #{cmd}"
      end
    
    end
    
    namespace :serverspec do
      hosts.each do |host|
        desc "Run serverspec to #{host[:name]}"
        ServerspecTask.new(host[:name].to_sym) do |t|
          t.target = host[:name]
          t.pattern = 'spec/{' + host[:roles].join(',') + '}/*_spec.rb'
        end
      end
    end
    

    And then finally, update your spec_helper.rb to read that environment variable and use it as the host:

    RSpec.configure do |c|
      c.host  = ENV['TARGET_HOST']
      options = Net::SSH::Config.for(c.host)
      user    = options[:user] || Etc.getlogin
      c.ssh   = Net::SSH.start(c.host, user, options)
      c.os    = backend.check_os
    end