rspec-system 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.travis.yml +13 -0
- data/Gemfile +9 -0
- data/LICENSE +17 -0
- data/README.md +87 -15
- data/lib/rspec-system.rb +4 -2
- data/lib/rspec-system/helpers.rb +172 -5
- data/lib/rspec-system/node.rb +76 -0
- data/lib/rspec-system/node_set/base.rb +38 -10
- data/lib/rspec-system/node_set/vagrant.rb +84 -29
- data/lib/rspec-system/prefab.rb +28 -0
- data/lib/rspec-system/rake_task.rb +1 -1
- data/lib/rspec-system/spec_helper.rb +30 -7
- data/resources/kwalify-schemas/prefabs_schema.yml +17 -0
- data/resources/prefabs.yml +43 -0
- data/rspec-system.gemspec +3 -4
- data/spec/spec_helper.rb +4 -1
- data/spec/unit/kwalify-schemas/prefabs_schema_spec.rb +25 -0
- metadata +14 -21
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/LICENSE
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
rspec-system - System testing using RSpec
|
2
|
+
|
3
|
+
Copyright (C) 2013 Puppet Labs Inc
|
4
|
+
|
5
|
+
Puppet Labs can be contacted at: info@puppetlabs.com
|
6
|
+
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
you may not use this file except in compliance with the License.
|
9
|
+
You may obtain a copy of the License at
|
10
|
+
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
See the License for the specific language governing permissions and
|
17
|
+
limitations under the License.
|
data/README.md
CHANGED
@@ -1,16 +1,12 @@
|
|
1
1
|
# rspec-system
|
2
2
|
|
3
|
-
|
3
|
+
`rspec-system` provides a framework for creating system tests using the `rspec` testing library.
|
4
4
|
|
5
|
-
|
6
|
-
* [Setup](#setup)
|
7
|
-
* [Tests](#tests)
|
5
|
+
The goal here is to provide facilities to aid in the launching of tests nodes, copying of test content to such nodes, and executing commands on such nodes to be tested with standard rspec assertions within the standard rspec test format.
|
8
6
|
|
9
|
-
|
7
|
+
*Note:* This library is fairly alpha at the moment, and the interface may change at without warning. That said, if you're good at ruby and have an opinion, I'd appreciate patches and improvements to move this further torwards stability.
|
10
8
|
|
11
|
-
###
|
12
|
-
|
13
|
-
#### Gem installation
|
9
|
+
### Gem installation
|
14
10
|
|
15
11
|
The intention is that this gem is used within your project as a development library.
|
16
12
|
|
@@ -26,20 +22,96 @@ Then installing with:
|
|
26
22
|
|
27
23
|
bundle install
|
28
24
|
|
29
|
-
|
25
|
+
### Writing tests
|
26
|
+
|
27
|
+
Start by creating a helper file in `spec/spec_helper_system.rb` containing something like the following:
|
28
|
+
|
29
|
+
require 'rspec-system/spec_helper'
|
30
|
+
|
31
|
+
RSpec.configure do |c|
|
32
|
+
c.system_setup_block = proc do
|
33
|
+
include RSpecSystem::Helpers
|
34
|
+
# Insert some setup tasks here
|
35
|
+
run('main', 'yum install -y ntp')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Create the directory `spec/system` in your project, make sure your unit tests go into `spec/unit` or somesuch so you can isolate them easily during test time. Add files with the spec prefix ie. `mytests_spec.rb` and make sure they always include the line `require 'spec_helper_system'` eg.:
|
40
|
+
|
41
|
+
require 'spec_helper_system'
|
42
|
+
|
43
|
+
describe 'basics' do
|
44
|
+
it 'should cat /etc/resolv.conf' do
|
45
|
+
run('main', 'cat /etc/resolv.conf') do |status,stdout,stderr|
|
46
|
+
stdout.should =~ /localhost/
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
Also consult the example in `example` in the source of this library for more details.
|
52
|
+
|
53
|
+
For you reference, here are the list of custom rspec configuration items that can be overriden in your `spec_helper_system.rb` file:
|
54
|
+
|
55
|
+
* *system_setup_block* - this accepts a proc that is called after node setup, but before every test (ie. before suite). The goal of this option is to provide a good place for node setup independant of tests.
|
56
|
+
* *system_tmp* - For some of our activity, we require a temporary file area. By default we just a random temporary path, so you normally do not need to set this.
|
57
|
+
|
58
|
+
Currently to get the nice formatting rspec-system specific formatter its recommended to use the Rake task, so the following to your `Rakefile`:
|
59
|
+
|
60
|
+
require 'rspec-system/rake_task'
|
30
61
|
|
31
|
-
|
62
|
+
That will setup the rake task `rake spec:system`.
|
32
63
|
|
33
|
-
###
|
64
|
+
### Creating a nodeset file
|
34
65
|
|
35
|
-
|
66
|
+
A nodeset file outlines all the node configurations for your tests. The concept here is to define one or more 'nodesets' each nodeset containing one or more 'nodes'.
|
36
67
|
|
37
|
-
|
68
|
+
---
|
69
|
+
default_set: 'centos-58-x64'
|
70
|
+
sets:
|
71
|
+
'centos-58-x64':
|
72
|
+
nodes:
|
73
|
+
"main":
|
74
|
+
prefab: 'centos-58-x64'
|
75
|
+
|
76
|
+
The file must adhere to the Kwalify schema supplied in `resources/kwalify-schemas/nodeset_schema.yml`.
|
77
|
+
|
78
|
+
### Prefabs
|
79
|
+
|
80
|
+
Prefabs are 'pre-rolled' virtual images, for now its the only way to do it.
|
81
|
+
|
82
|
+
The current prefabs are defined in `resources/prefabs.yml`.
|
83
|
+
|
84
|
+
### Running tests
|
38
85
|
|
39
86
|
Run the system tests with:
|
40
87
|
|
41
88
|
rake spec:system
|
42
89
|
|
43
|
-
|
90
|
+
Instead of switches, we use a number of environment variables to modify the behaviour of running tests. This is more inline with the way testing frameworks like Jenkins work, and should be pretty easy for command line users as well:
|
91
|
+
|
92
|
+
* *RSPEC_VIRTUAL_ENV* - the type of virtual environment to run (currently `vagrant` is the only option)
|
93
|
+
* *RSPEC_SET* - the set to use when running tests (defaults to the `default_set` setting in the projects `.nodeset.yml` file)
|
94
|
+
|
95
|
+
So if you wanted to run an alternate nodeset you could use:
|
96
|
+
|
97
|
+
RSPEC_SET=nodeset2 rake spec:system
|
98
|
+
|
99
|
+
In Jenkins you should be able to use RSPEC\_SET in a test matrix, thus obtaining quite a nice integration and visual display of nodesets in Jenkins.
|
100
|
+
|
101
|
+
### Plugins to rspec-system
|
102
|
+
|
103
|
+
I want to start an eco-system of plugins for rspec-system, but do it in a sane way. Right now I see the following potential plugin types, if you think you can help please do:
|
104
|
+
|
105
|
+
* nodes providers - that is, abstractions around other virtualisation tools. Right now a NodeSet is tied to a virtual type, but I think this isn't granual enough.
|
106
|
+
* blimpy - for firing up EC2 and OpenStack nodes, useful for Jenkins integration
|
107
|
+
* vmware - for those who have VMWare virtual 'clouds' or boxen
|
108
|
+
* razor - for launching hardware nodes.
|
109
|
+
* manual - not everything has to be 'launched' I can see a need for defining a static configuration for older machines that can't be poked and peeked.
|
110
|
+
* helper libraries - libraries that provide test helpers, and setup helpers for testing development on the software in question.
|
111
|
+
* distro - helpers that wrap common linux distro tasks, like package installation.
|
112
|
+
* puppet - helpers around installing different versions of puppet, PE as well - firing up masters. Perfect for testing modules I think.
|
113
|
+
* mcollective - for launching the basics, activemq, broker clusters. Useful for testing mcollective agents.
|
114
|
+
* puppetdb - helpers for setting up puppetdb, probably using the modules.
|
115
|
+
* others I'm sure ...
|
44
116
|
|
45
|
-
|
117
|
+
These could be shipped as external gems, and plugged in to the rspec-system framework somehow.
|
data/lib/rspec-system.rb
CHANGED
@@ -5,12 +5,14 @@ module RSpecSystem; end
|
|
5
5
|
require 'rspec-system/log'
|
6
6
|
require 'rspec-system/helpers'
|
7
7
|
require 'rspec-system/node_set'
|
8
|
+
require 'rspec-system/prefab'
|
9
|
+
require 'rspec-system/node'
|
8
10
|
|
9
11
|
RSpec::configure do |c|
|
10
12
|
c.include RSpecSystem::Helpers
|
11
13
|
|
12
14
|
# This provides a path to save vagrant files
|
13
15
|
c.add_setting :system_tmp
|
14
|
-
#
|
15
|
-
c.add_setting :
|
16
|
+
# Block to execute for environment setup
|
17
|
+
c.add_setting :system_setup_block
|
16
18
|
end
|
data/lib/rspec-system/helpers.rb
CHANGED
@@ -1,8 +1,175 @@
|
|
1
|
+
# This module contains the main rspec helpers that are to be used within
|
2
|
+
# rspec-system tests.
|
3
|
+
#
|
4
|
+
# The methods here-in are accessible within your rspec tests and can also
|
5
|
+
# be used within your setup blocks as well.
|
6
|
+
#
|
7
|
+
# These helpers in particular are core to the framework. You can however
|
8
|
+
# combined these helpers to create your own more powerful helpers in rspec
|
9
|
+
# if you wish.
|
10
|
+
#
|
11
|
+
# @example Using run within your tests
|
12
|
+
# describe 'test running' do
|
13
|
+
# it 'run cat' do
|
14
|
+
# run 'cat /etc/resolv.conf' do |status, out, err|
|
15
|
+
# status.exitstatus.should == 0
|
16
|
+
# stdout.should =~ /localhost/
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
# @example Using rcp in your tests
|
21
|
+
# describe 'test running' do
|
22
|
+
# it 'copy my files' do
|
23
|
+
# rcp :sp => 'mydata', :dp => '/srv/data'.should be_true
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
# @example Using node in your tests
|
27
|
+
# describe 'test running' do
|
28
|
+
# it 'do something if redhat' do
|
29
|
+
# if node.facts[:operatingsystem] == 'RedHat' do
|
30
|
+
# run 'cat /etc/redhat-release'
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
# end
|
1
34
|
module RSpecSystem::Helpers
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
35
|
+
# @!group Actions
|
36
|
+
|
37
|
+
# Runs a shell command on a test host, returning status, stdout and stderr.
|
38
|
+
#
|
39
|
+
# When invoked as a block the status,stdout and stderr are yielded to the
|
40
|
+
# block as parameters.
|
41
|
+
#
|
42
|
+
# If you have only provided 1 node in your nodeset, or you have specified a
|
43
|
+
# a default you can avoid entering the name of the node if you wish.
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
# @param options [Hash, String] options for command execution, if passed a
|
47
|
+
# string it will just use that for the command instead as a convenience.
|
48
|
+
# @option options [String] :command command to execute. Mandatory.
|
49
|
+
# @option options [String] :c alias for :command
|
50
|
+
# @option options [RSpecSystem::Node] :node (defaults to what was defined
|
51
|
+
# default in your YAML file, otherwise if there is only one node it uses
|
52
|
+
# that) specifies node to execute command on.
|
53
|
+
# @option options [RSpecSystem::Node] :n alias for :node
|
54
|
+
# @yield [status, stdout, stderr] yields status, stdout and stderr when
|
55
|
+
# called as a block.
|
56
|
+
# @yieldparam status [Process::Status] the status of the executed command
|
57
|
+
# @yieldparam stdout [String] the standard out of the command result
|
58
|
+
# @yieldparam stderr [String] the standard error of the command result
|
59
|
+
# @return [Array<Process::Status,String,String>] returns status, stdout and
|
60
|
+
# stderr when called as a simple method.
|
61
|
+
def run(options)
|
62
|
+
ns = rspec_system_node_set
|
63
|
+
dn = ns.default_node
|
64
|
+
|
65
|
+
# Take options as a string instead
|
66
|
+
if options.is_a?(String)
|
67
|
+
options = {:c => options}
|
68
|
+
end
|
69
|
+
|
70
|
+
options = {
|
71
|
+
:node => options[:n] || dn,
|
72
|
+
:n => options[:node] || dn,
|
73
|
+
:c => options[:command],
|
74
|
+
:command => options[:c],
|
75
|
+
}.merge(options)
|
76
|
+
|
77
|
+
if options[:c].nil?
|
78
|
+
raise "Cannot use run with no :command option"
|
79
|
+
end
|
80
|
+
|
81
|
+
log.info("run #{options[:c]} on #{options[:n].name} executed")
|
82
|
+
status, stdout, stderr = result = ns.run(options)
|
83
|
+
log.info("run results:\n" +
|
84
|
+
"-----------------------\n" +
|
85
|
+
"Exit Status: #{status.exitstatus}\n" +
|
86
|
+
"<stdout>#{stdout}</stdout>\n" +
|
87
|
+
"<stderr>#{stderr}</stderr>\n" +
|
88
|
+
"-----------------------\n")
|
89
|
+
|
90
|
+
if block_given?
|
91
|
+
yield(*result)
|
92
|
+
else
|
93
|
+
result
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Remotely copy files to a test node. This will use the underlying nodes
|
98
|
+
# rcp mechanism to do the transfer for you, so you generally shouldn't
|
99
|
+
# need to consider the implementation.
|
100
|
+
#
|
101
|
+
# Just specify a source, and a destination path, and go.
|
102
|
+
#
|
103
|
+
# @example Remote copy /srv/data to remote host
|
104
|
+
# rcp(:dest_path => '/srv/data', :source_path => 'mydata')
|
105
|
+
# @param options [Hash] options for command execution
|
106
|
+
# @option options [String] :source_path source to copy files from (currently
|
107
|
+
# only locally)
|
108
|
+
# @option options [String] :sp alias for source_path
|
109
|
+
# @option options [String] :destination_path destination for copy
|
110
|
+
# @option options [String] :dp alias for dest_path
|
111
|
+
# @option options [RSpecSystem::Node] :destination_node (default_node) destination node
|
112
|
+
# to transfer files to. Optional.
|
113
|
+
# @option options [RSpecSystem::Node] :d alias for destination_node
|
114
|
+
# @option options [RSpecSystem::Node] :source_node ('') Reserved
|
115
|
+
# for future use. Patches welcome.
|
116
|
+
# @option options [RSpecSystem::Node] :s alias for source_node
|
117
|
+
# @return [Bool] returns true if successful
|
118
|
+
# @todo Need to create some helpers for validating input and creating default,
|
119
|
+
# aliases and bloody yarddocs from some other magic format. Ideas?
|
120
|
+
# @todo Support system to system copy using source_node option.
|
121
|
+
def rcp(options)
|
122
|
+
options = {
|
123
|
+
:source_path => options[:sp],
|
124
|
+
:destination_path => options[:dp],
|
125
|
+
:dp => options[:destination_path],
|
126
|
+
:sp => options[:source_path],
|
127
|
+
:destination_node => rspec_system_node_set.default_node,
|
128
|
+
:d => rspec_system_node_set.default_node,
|
129
|
+
:source_node => '',
|
130
|
+
:s => '',
|
131
|
+
}.merge(options)
|
132
|
+
|
133
|
+
d = options[:d]
|
134
|
+
sp = options[:sp]
|
135
|
+
dp = options[:dp]
|
136
|
+
|
137
|
+
log.info("rcp from #{sp} to #{d.name}:#{dp} executed")
|
138
|
+
status, stdout, stderr = results = rspec_system_node_set.rcp(options)
|
139
|
+
log.info("rcp results:\n" +
|
140
|
+
"-----------------------\n" +
|
141
|
+
"Exit Status: #{status.exitstatus}\n" +
|
142
|
+
"<stdout>#{stdout}</stdout>\n" +
|
143
|
+
"<stderr>#{stderr}</stderr>\n" +
|
144
|
+
"-----------------------\n")
|
145
|
+
|
146
|
+
if status.exitstatus == 1
|
147
|
+
return true
|
148
|
+
else
|
149
|
+
return false
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# @!group Queries
|
154
|
+
|
155
|
+
# Returns a particular node object from the current nodeset given a set of
|
156
|
+
# criteria.
|
157
|
+
#
|
158
|
+
# If no options are supplied, it tries to return the default node.
|
159
|
+
#
|
160
|
+
# @param options [Hash] search criteria
|
161
|
+
# @option options [String] :name the canonical name of the node
|
162
|
+
# @return [RSpecSystem::Node] node object
|
163
|
+
def node(options = {})
|
164
|
+
ns = rspec_system_node_set
|
165
|
+
options = {
|
166
|
+
:name => ns.default_node,
|
167
|
+
}.merge(options)
|
168
|
+
|
169
|
+
if !options[:name].nil?
|
170
|
+
return ns.nodes[options[:name]]
|
171
|
+
else
|
172
|
+
raise "No nodes to return"
|
173
|
+
end
|
7
174
|
end
|
8
175
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module RSpecSystem
|
2
|
+
# This class represents a node in a nodeset
|
3
|
+
class Node
|
4
|
+
# Static helper for generating a node direct from the hash returned by
|
5
|
+
# the nodeset YAML file.
|
6
|
+
#
|
7
|
+
# @param nodeset [RSpecSystem::Node] nodeset that this node belongs to
|
8
|
+
# @param k [String] name of node
|
9
|
+
# @param v [Hash<String,String>] hash configuration as given from the nodeset yaml file
|
10
|
+
# @return [RSpecSystem::Node] returns a new node object
|
11
|
+
def self.node_from_yaml(nodeset, k, v)
|
12
|
+
RSpecSystem::Node.new(
|
13
|
+
:nodeset => nodeset,
|
14
|
+
:name => k,
|
15
|
+
:prefab => v['prefab']
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Create a new node object.
|
20
|
+
#
|
21
|
+
# @param options [Hash] options for new node
|
22
|
+
# @option options [String] :name name of node. Mandatory.
|
23
|
+
# @option options [String] :prefab prefab setting. Mandatory.
|
24
|
+
# @option options [RSpecSystem::NodeSet] :nodeset the parent nodeset for
|
25
|
+
# this node. Mandatory.
|
26
|
+
def initialize(options)
|
27
|
+
@name = options[:name]
|
28
|
+
prefab = options[:prefab]
|
29
|
+
@nodeset = options[:nodeset]
|
30
|
+
|
31
|
+
if prefab.nil?
|
32
|
+
# TODO: do not support not prefabs yet
|
33
|
+
raise "No prefab defined, bailing"
|
34
|
+
else
|
35
|
+
@prefab = RSpecSystem::Prefab.prefab(prefab)
|
36
|
+
@facts = @prefab.facts
|
37
|
+
@provider_specifics = @prefab.provider_specifics
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the name of the node as specified in the nodeset file.
|
42
|
+
#
|
43
|
+
# @return [String] name of node
|
44
|
+
def name
|
45
|
+
@name
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the prefab object for this node (if any).
|
49
|
+
#
|
50
|
+
# @return [RSpecSystem::Prefab] the prefab object used to create this node
|
51
|
+
def prefab
|
52
|
+
@prefab
|
53
|
+
end
|
54
|
+
|
55
|
+
# Retreives facts from the nodeset definition or prefab.
|
56
|
+
#
|
57
|
+
# @return [Hash] returns a hash of facter facts defined for this node
|
58
|
+
def facts
|
59
|
+
@facts
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the nodeset this node belongs in.
|
63
|
+
#
|
64
|
+
# @return [RSpecSystem::NodeSet] the nodeset this node belongs to
|
65
|
+
def nodeset
|
66
|
+
@nodeset
|
67
|
+
end
|
68
|
+
|
69
|
+
# Return provider specific settings
|
70
|
+
#
|
71
|
+
# @return [Hash] provider specific settings
|
72
|
+
def provider_specifics
|
73
|
+
@provider_specifics
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -1,36 +1,64 @@
|
|
1
1
|
module RSpecSystem
|
2
|
-
# Base class for a NodeSet.
|
2
|
+
# Base class for a NodeSet driver. If you want to create a new driver, you
|
3
|
+
# should sub-class this and override all the methods below.
|
4
|
+
#
|
5
|
+
# @abstract Subclass and override methods to create a new NodeSet variant.
|
3
6
|
class NodeSet::Base
|
4
|
-
attr_reader :config
|
7
|
+
attr_reader :config
|
8
|
+
attr_reader :setname
|
9
|
+
attr_reader :nodes
|
5
10
|
|
11
|
+
# Create new NodeSet, populating necessary data structures.
|
6
12
|
def initialize(setname, config)
|
7
13
|
@setname = setname
|
8
14
|
@config = config
|
15
|
+
|
16
|
+
@nodes = {}
|
17
|
+
config['nodes'].each do |k,v|
|
18
|
+
@nodes[k] = RSpecSystem::Node.node_from_yaml(self, k, v)
|
19
|
+
end
|
9
20
|
end
|
10
21
|
|
11
22
|
# Setup the NodeSet by starting all nodes.
|
12
23
|
def setup
|
24
|
+
raise "Unimplemented method #setup"
|
13
25
|
end
|
14
26
|
|
15
27
|
# Shutdown the NodeSet by shutting down or pausing all nodes.
|
16
28
|
def teardown
|
29
|
+
raise "Unimplemented method #teardown"
|
17
30
|
end
|
18
31
|
|
19
|
-
#
|
20
|
-
def
|
32
|
+
# Run a command on a host in the NodeSet.
|
33
|
+
def run(options)
|
34
|
+
raise "Unimplemented method #run"
|
21
35
|
end
|
22
36
|
|
23
|
-
#
|
24
|
-
def
|
25
|
-
|
26
|
-
|
27
|
-
# Run a command on a host in the NodeSet.
|
28
|
-
def run(dest, command)
|
37
|
+
# Copy a file to the host in the NodeSet.
|
38
|
+
def rcp(options)
|
39
|
+
raise "Unimplemented method #rcp"
|
29
40
|
end
|
30
41
|
|
31
42
|
# Return environment type
|
32
43
|
def env_type
|
33
44
|
self.class::ENV_TYPE
|
34
45
|
end
|
46
|
+
|
47
|
+
# Return default node
|
48
|
+
#
|
49
|
+
# @return [RSpecSystem::Node] default node for this nodeset
|
50
|
+
def default_node
|
51
|
+
dn = config['default_node']
|
52
|
+
if dn.nil?
|
53
|
+
if nodes.length == 1
|
54
|
+
dn = nodes.first[1]
|
55
|
+
return dn
|
56
|
+
else
|
57
|
+
raise "No default node"
|
58
|
+
end
|
59
|
+
else
|
60
|
+
return nodes[dn]
|
61
|
+
end
|
62
|
+
end
|
35
63
|
end
|
36
64
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'fileutils'
|
2
|
+
require 'systemu'
|
2
3
|
|
3
4
|
module RSpecSystem
|
4
5
|
# A NodeSet implementation for Vagrant.
|
@@ -14,76 +15,130 @@ module RSpecSystem
|
|
14
15
|
|
15
16
|
# Setup the NodeSet by starting all nodes.
|
16
17
|
def setup
|
17
|
-
log.info "Begin setting up vagrant"
|
18
|
+
log.info "[Vagrant#setup] Begin setting up vagrant"
|
18
19
|
create_vagrantfile
|
19
20
|
|
20
|
-
log.info "Running 'vagrant destroy'"
|
21
|
-
vagrant("destroy
|
21
|
+
log.info "[Vagrant#setup] Running 'vagrant destroy'"
|
22
|
+
vagrant("destroy --force")
|
22
23
|
|
23
|
-
log.info "Running 'vagrant up'"
|
24
|
+
log.info "[Vagrant#setup] Running 'vagrant up'"
|
24
25
|
vagrant("up")
|
25
26
|
end
|
26
27
|
|
27
28
|
# Shutdown the NodeSet by shutting down or pausing all nodes.
|
28
29
|
def teardown
|
29
|
-
log.info "Running 'vagrant destroy'"
|
30
|
-
vagrant("destroy
|
30
|
+
log.info "[Vagrant#teardown] Running 'vagrant destroy'"
|
31
|
+
vagrant("destroy --force")
|
31
32
|
end
|
32
33
|
|
33
34
|
# Run a command on a host in the NodeSet.
|
34
|
-
|
35
|
+
#
|
36
|
+
# @param opts [Hash] options
|
37
|
+
def run(opts)
|
38
|
+
#log.debug("[Vagrant#run] called with #{opts.inspect}")
|
39
|
+
|
40
|
+
dest = opts[:n].name
|
41
|
+
cmd = opts[:c]
|
42
|
+
|
35
43
|
result = ""
|
36
44
|
Dir.chdir(@vagrant_path) do
|
37
|
-
|
45
|
+
cmd = "vagrant ssh #{dest} --command \"cd /tmp && sudo -i #{cmd}\""
|
46
|
+
log.debug("[vagrant#run] Running command: #{cmd}")
|
47
|
+
result = systemu cmd
|
48
|
+
log.debug("[Vagrant#run] Finished running command: #{cmd}. Result is #{result}.")
|
38
49
|
end
|
39
50
|
result
|
40
51
|
end
|
41
52
|
|
53
|
+
# Transfer files to a host in the NodeSet.
|
54
|
+
#
|
55
|
+
# @param opts [Hash] options
|
56
|
+
def rcp(opts)
|
57
|
+
#log.debug("[Vagrant@rcp] called with #{opts.inspect}")
|
58
|
+
|
59
|
+
dest = opts[:d].name
|
60
|
+
source = opts[:sp]
|
61
|
+
dest_path = opts[:dp]
|
62
|
+
|
63
|
+
# TODO: This is damn ugly, because we ssh in as vagrant, we copy to a
|
64
|
+
# temp path then move later. This pattern at the moment only really works
|
65
|
+
# on dirs.
|
66
|
+
log.info("[Vagrant#rcp] Transferring files from #{source} to #{dest}:#{dest_path}")
|
67
|
+
|
68
|
+
# TODO: The static temp path here is definately insecure
|
69
|
+
cmd = "scp -r -F #{ssh_config} #{source} #{dest}:/tmp/tmpxfer"
|
70
|
+
log.debug("[Vagrant#rcp] Running command: #{cmd}")
|
71
|
+
systemu cmd
|
72
|
+
|
73
|
+
# Now we move the file into place
|
74
|
+
run(:n => opts[:d], :c => "mv /tmp/tmpxfer #{dest_path}")
|
75
|
+
end
|
76
|
+
|
42
77
|
# Create the Vagrantfile for the NodeSet.
|
78
|
+
#
|
43
79
|
# @api private
|
44
80
|
def create_vagrantfile
|
45
|
-
log.info "Creating vagrant file here: #{@vagrant_path}"
|
81
|
+
log.info "[Vagrant#create_vagrantfile] Creating vagrant file here: #{@vagrant_path}"
|
46
82
|
FileUtils.mkdir_p(@vagrant_path)
|
47
83
|
File.open(File.expand_path(File.join(@vagrant_path, "Vagrantfile")), 'w') do |f|
|
48
84
|
f.write('Vagrant::Config.run do |c|')
|
49
|
-
|
85
|
+
nodes.each do |k,v|
|
50
86
|
log.debug "Filling in content for #{k}"
|
51
87
|
f.write(<<-EOS)
|
52
88
|
c.vm.define '#{k}' do |vmconf|
|
53
|
-
#{
|
89
|
+
vmconf.vm.host_name = "#{k}"
|
90
|
+
#{template_node(v.provider_specifics['vagrant'])}
|
54
91
|
end
|
55
92
|
EOS
|
56
93
|
end
|
57
94
|
f.write('end')
|
58
95
|
end
|
59
|
-
log.debug "Finished creating vagrant file"
|
96
|
+
log.debug "[Vagrant#create_vagrantfile] Finished creating vagrant file"
|
60
97
|
end
|
61
98
|
|
62
|
-
#
|
99
|
+
# Here we get vagrant to drop the ssh_config its using so we can monopolize
|
100
|
+
# it for transfers and custom stuff. We drop it into a single file, and
|
101
|
+
# since its indexed based on our own node names its quite ideal.
|
102
|
+
#
|
63
103
|
# @api private
|
64
|
-
def
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
EOS
|
76
|
-
else
|
77
|
-
raise 'Unknown prefab'
|
104
|
+
def ssh_config
|
105
|
+
ssh_config_path = File.expand_path(File.join(@vagrant_path, "ssh_config"))
|
106
|
+
begin
|
107
|
+
File.unlink(ssh_config_path)
|
108
|
+
rescue Errno::ENOENT
|
109
|
+
end
|
110
|
+
self.nodes.each do |k,v|
|
111
|
+
Dir.chdir(@vagrant_path) do
|
112
|
+
result = systemu("vagrant ssh-config #{k} >> #{ssh_config_path}")
|
113
|
+
puts result.inspect
|
114
|
+
end
|
78
115
|
end
|
116
|
+
ssh_config_path
|
117
|
+
end
|
118
|
+
|
119
|
+
# Provide Vagrantfile templates from node definition.
|
120
|
+
#
|
121
|
+
# @api private
|
122
|
+
# @param settings [Hash] provider specific settings for vagrant
|
123
|
+
def template_node(settings)
|
124
|
+
template = <<-EOS
|
125
|
+
vmconf.vm.box = '#{settings['box']}'
|
126
|
+
vmconf.vm.box_url = '#{settings['box_url']}'
|
127
|
+
EOS
|
79
128
|
end
|
80
129
|
|
81
130
|
# Execute vagrant command in vagrant_path
|
131
|
+
#
|
82
132
|
# @api private
|
83
|
-
|
133
|
+
# @param args [String] args to vagrant
|
134
|
+
# @todo This seems a little too specific these days, might want to
|
135
|
+
# generalize. It doesn't use systemu, because we want to see the output
|
136
|
+
# immediately, but still - maybe we can make systemu do that.
|
137
|
+
def vagrant(args)
|
84
138
|
Dir.chdir(@vagrant_path) do
|
85
|
-
system("vagrant
|
139
|
+
system("vagrant #{args}")
|
86
140
|
end
|
141
|
+
nil
|
87
142
|
end
|
88
143
|
end
|
89
144
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module RSpecSystem
|
2
|
+
class Prefab
|
3
|
+
attr_reader :name
|
4
|
+
attr_reader :description
|
5
|
+
attr_reader :facts
|
6
|
+
attr_reader :provider_specifics
|
7
|
+
|
8
|
+
# Return prefab object based on name
|
9
|
+
def self.prefab(name)
|
10
|
+
prefabs = YAML.load_file(File.join(File.dirname(__FILE__), '..', '..', 'resources', 'prefabs.yml'))
|
11
|
+
raise "No such prefab" unless pf = prefabs[name]
|
12
|
+
|
13
|
+
RSpecSystem::Prefab.new(
|
14
|
+
:name => name,
|
15
|
+
:description => pf['description'],
|
16
|
+
:facts => pf['facts'],
|
17
|
+
:provider_specifics => pf['provider_specifics']
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(options = {})
|
22
|
+
@name = options[:name]
|
23
|
+
@description = options[:description]
|
24
|
+
@facts = options[:facts]
|
25
|
+
@provider_specifics = options[:provider_specifics]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -7,7 +7,7 @@ require 'rspec/core/rake_task'
|
|
7
7
|
require 'rspec-system/formatter'
|
8
8
|
|
9
9
|
RSpec::Core::RakeTask.new(:spec_system) do |c|
|
10
|
-
c.pattern = "spec/system
|
10
|
+
c.pattern = "spec/system/**/*_spec.rb"
|
11
11
|
c.rspec_opts = %w[--require rspec-system/formatter --format=RSpecSystem::Formatter]
|
12
12
|
end
|
13
13
|
|
@@ -25,18 +25,41 @@ RSpec.configure do |c|
|
|
25
25
|
RSpecSystem::NodeSet.create(setname, config, rspec_virtual_env)
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
28
|
+
def start_nodes
|
29
|
+
ns = rspec_system_node_set
|
30
|
+
|
30
31
|
log.info "START RSPEC-SYSTEM SETUP"
|
31
|
-
log.info "Setname is: " +
|
32
|
-
log.info "Configuration is: " +
|
33
|
-
log.info "Virtual Environment type is: #{
|
32
|
+
log.info "Setname is: " + ns.setname
|
33
|
+
log.info "Configuration is: " + ns.config.pretty_inspect
|
34
|
+
log.info "Virtual Environment type is: #{ns.env_type}"
|
35
|
+
log.info "Default node is: #{ns.default_node.name}"
|
34
36
|
|
35
|
-
|
37
|
+
ns.setup
|
36
38
|
end
|
37
39
|
|
38
|
-
|
40
|
+
def stop_nodes
|
39
41
|
log.info 'FINALIZE RSPEC-SYSTEM SETUP'
|
40
42
|
rspec_system_node_set.teardown
|
41
43
|
end
|
44
|
+
|
45
|
+
def call_custom_setup_block
|
46
|
+
# Run test specific setup routines
|
47
|
+
if pr = RSpec.configuration.system_setup_block then
|
48
|
+
log.info "Running custom setup block"
|
49
|
+
pr.call
|
50
|
+
log.info "Finished running custom setup block"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Default the system_tmp dir to something random
|
55
|
+
c.system_tmp = Dir.tmpdir
|
56
|
+
|
57
|
+
c.before :suite do
|
58
|
+
start_nodes
|
59
|
+
call_custom_setup_block
|
60
|
+
end
|
61
|
+
|
62
|
+
c.after :suite do
|
63
|
+
stop_nodes
|
64
|
+
end
|
42
65
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
---
|
2
|
+
'centos-58-x64':
|
3
|
+
description: "Vagrant images obtained from http://puppet-vagrant-boxes.puppetlabs.com"
|
4
|
+
facts:
|
5
|
+
architecture: x86_64
|
6
|
+
facterversion: "1.6.17"
|
7
|
+
kernel: Linux
|
8
|
+
kernelmajversion: "2.6"
|
9
|
+
kernelrelease: "2.6.18-308.el5"
|
10
|
+
kernelversion: "2.6.18"
|
11
|
+
lsbdistcodename: Final
|
12
|
+
lsbdistdescription: "CentOS release 5.8 (Final)"
|
13
|
+
lsbdistid: CentOS
|
14
|
+
lsbdistrelease: "5.8"
|
15
|
+
lsbmajdistrelease: "5"
|
16
|
+
lsbrelease: ":core-4.0-amd64:core-4.0-ia32:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-ia32:graphics-4.0-noarch:printing-4.0-amd64:printing-4.0-ia32:printing-4.0-noarch"
|
17
|
+
operatingsystem: CentOS
|
18
|
+
operatingsystemrelease: "5.8"
|
19
|
+
osfamily: RedHat
|
20
|
+
rubyversion: "1.9.2"
|
21
|
+
provider_specifics:
|
22
|
+
vagrant:
|
23
|
+
box: 'centos-58-x64'
|
24
|
+
box_url: 'http://puppet-vagrant-boxes.puppetlabs.com/centos-58-x64.box'
|
25
|
+
'debian-606-x64':
|
26
|
+
description: "Vagrant images obtained from http://puppet-vagrant-boxes.puppetlabs.com"
|
27
|
+
facts:
|
28
|
+
architecture: amd64
|
29
|
+
facterversion: "1.6.17"
|
30
|
+
kernel: Linux
|
31
|
+
kernelmajversion: "2.6"
|
32
|
+
kernelversion: "2.6.32"
|
33
|
+
lsbdistcodename: squeeze
|
34
|
+
lsbdistdescription: "Debian GNU/Linux 6.0.6 (squeeze)"
|
35
|
+
lsbdistid: Debian
|
36
|
+
lsbdistrelease: "6.0.6"
|
37
|
+
operatingsystem: Debian
|
38
|
+
osfamily: Debian
|
39
|
+
rubyversion: "1.8.7"
|
40
|
+
provider_specifics:
|
41
|
+
vagrant:
|
42
|
+
box: 'debian-606-x64'
|
43
|
+
box_url: 'http://puppet-vagrant-boxes.puppetlabs.com/debian-606-x64.box'
|
data/rspec-system.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
Gem::Specification.new do |s|
|
3
3
|
# Metadata
|
4
4
|
s.name = "rspec-system"
|
5
|
-
s.version = "0.0
|
5
|
+
s.version = "0.1.0"
|
6
6
|
s.authors = ["Ken Barber"]
|
7
7
|
s.email = ["ken@bob.sh"]
|
8
8
|
s.homepage = "https://github.com/kbarber/rspec-system"
|
@@ -15,9 +15,8 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.require_paths = ["lib"]
|
16
16
|
|
17
17
|
# Dependencies
|
18
|
-
s.required_ruby_version = '>= 1.
|
18
|
+
s.required_ruby_version = '>= 1.8.7'
|
19
19
|
s.add_runtime_dependency "rspec"
|
20
20
|
s.add_runtime_dependency "kwalify"
|
21
|
-
s.
|
22
|
-
s.add_development_dependency "mocha"
|
21
|
+
s.add_runtime_dependency "systemu"
|
23
22
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -16,8 +16,11 @@ end
|
|
16
16
|
def fixture_path
|
17
17
|
Pathname.new(File.expand_path(File.join(__FILE__, '..', 'fixtures')))
|
18
18
|
end
|
19
|
+
def resources_path
|
20
|
+
Pathname.new(File.expand_path(File.join(__FILE__, '..', '..', 'resources')))
|
21
|
+
end
|
19
22
|
def schema_path
|
20
|
-
|
23
|
+
resources_path + 'kwalify-schemas'
|
21
24
|
end
|
22
25
|
|
23
26
|
RSpec.configure do |config|
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'kwalify'
|
3
|
+
|
4
|
+
describe 'prefabs_schema' do
|
5
|
+
let(:schema) do
|
6
|
+
YAML.load_file(schema_path + 'prefabs_schema.yml')
|
7
|
+
end
|
8
|
+
let(:validator) do
|
9
|
+
validator = Kwalify::Validator.new(schema)
|
10
|
+
end
|
11
|
+
let(:parser) do
|
12
|
+
parser = Kwalify::Yaml::Parser.new(validator)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should not return an error for prefabs.yml" do
|
16
|
+
ydoc = parser.parse_file(resources_path + 'prefabs.yml')
|
17
|
+
errors = parser.errors
|
18
|
+
if errors && !errors.empty?
|
19
|
+
errors.each do |e|
|
20
|
+
puts "line=#{e.linenum}, path=#{e.path}, mesg=#{e.message}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
errors.should == []
|
24
|
+
end
|
25
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspec-system
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -44,30 +44,14 @@ dependencies:
|
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '0'
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
|
-
name:
|
47
|
+
name: systemu
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|
49
49
|
none: false
|
50
50
|
requirements:
|
51
51
|
- - ! '>='
|
52
52
|
- !ruby/object:Gem::Version
|
53
53
|
version: '0'
|
54
|
-
type: :
|
55
|
-
prerelease: false
|
56
|
-
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
|
-
requirements:
|
59
|
-
- - ! '>='
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
- !ruby/object:Gem::Dependency
|
63
|
-
name: mocha
|
64
|
-
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
|
-
requirements:
|
67
|
-
- - ! '>='
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: '0'
|
70
|
-
type: :development
|
54
|
+
type: :runtime
|
71
55
|
prerelease: false
|
72
56
|
version_requirements: !ruby/object:Gem::Requirement
|
73
57
|
none: false
|
@@ -84,7 +68,9 @@ extra_rdoc_files: []
|
|
84
68
|
files:
|
85
69
|
- .gitignore
|
86
70
|
- .ruby-version
|
71
|
+
- .travis.yml
|
87
72
|
- Gemfile
|
73
|
+
- LICENSE
|
88
74
|
- README.md
|
89
75
|
- examples/.gitignore
|
90
76
|
- examples/.nodeset.yml
|
@@ -99,17 +85,22 @@ files:
|
|
99
85
|
- lib/rspec-system/formatter.rb
|
100
86
|
- lib/rspec-system/helpers.rb
|
101
87
|
- lib/rspec-system/log.rb
|
88
|
+
- lib/rspec-system/node.rb
|
102
89
|
- lib/rspec-system/node_set.rb
|
103
90
|
- lib/rspec-system/node_set/base.rb
|
104
91
|
- lib/rspec-system/node_set/vagrant.rb
|
92
|
+
- lib/rspec-system/prefab.rb
|
105
93
|
- lib/rspec-system/rake_task.rb
|
106
94
|
- lib/rspec-system/spec_helper.rb
|
107
95
|
- resources/kwalify-schemas/nodeset_schema.yml
|
96
|
+
- resources/kwalify-schemas/prefabs_schema.yml
|
97
|
+
- resources/prefabs.yml
|
108
98
|
- rspec-system.gemspec
|
109
99
|
- spec/fixtures/nodeset_example1.yml
|
110
100
|
- spec/fixtures/nodeset_example2.yml
|
111
101
|
- spec/spec_helper.rb
|
112
102
|
- spec/unit/kwalify-schemas/nodeset_schema_spec.rb
|
103
|
+
- spec/unit/kwalify-schemas/prefabs_schema_spec.rb
|
113
104
|
homepage: https://github.com/kbarber/rspec-system
|
114
105
|
licenses: []
|
115
106
|
post_install_message:
|
@@ -121,7 +112,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
121
112
|
requirements:
|
122
113
|
- - ! '>='
|
123
114
|
- !ruby/object:Gem::Version
|
124
|
-
version: 1.
|
115
|
+
version: 1.8.7
|
125
116
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
117
|
none: false
|
127
118
|
requirements:
|
@@ -136,3 +127,5 @@ specification_version: 3
|
|
136
127
|
summary: System testing with rspec
|
137
128
|
test_files:
|
138
129
|
- spec/unit/kwalify-schemas/nodeset_schema_spec.rb
|
130
|
+
- spec/unit/kwalify-schemas/prefabs_schema_spec.rb
|
131
|
+
has_rdoc:
|