spex 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +4 -0
- data/LICENSE +20 -0
- data/README.markdown +116 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/bin/spex +7 -0
- data/examples/chgrp/run.sh +3 -0
- data/examples/chgrp/scenarios.rb +5 -0
- data/examples/chmod/run.sh +3 -0
- data/examples/chmod/scenarios.rb +5 -0
- data/examples/chown/run.sh +3 -0
- data/examples/chown/scenarios.rb +5 -0
- data/examples/puppet/manifest.pp +3 -0
- data/examples/puppet/run.sh +3 -0
- data/examples/puppet/scenarios.rb +5 -0
- data/examples/touch/run.sh +3 -0
- data/examples/touch/scenarios.rb +5 -0
- data/lib/spex.rb +9 -0
- data/lib/spex/assertion.rb +49 -0
- data/lib/spex/assertions/chgrps_file_assertion.rb +81 -0
- data/lib/spex/assertions/chmods_file_assertion.rb +56 -0
- data/lib/spex/assertions/chowns_file_assertion.rb +81 -0
- data/lib/spex/assertions/creates_file_assertion.rb +31 -0
- data/lib/spex/assertions/file_assertion.rb +29 -0
- data/lib/spex/assertions/removes_file_assertion.rb +31 -0
- data/lib/spex/cli.rb +50 -0
- data/lib/spex/runner.rb +89 -0
- data/lib/spex/scenario.rb +24 -0
- data/lib/spex/script.rb +52 -0
- data/lib/spex/script/builder.rb +15 -0
- data/test/helper.rb +10 -0
- data/test/test_stringup.rb +7 -0
- metadata +124 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Bruce Williams
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
Spex
|
2
|
+
========
|
3
|
+
|
4
|
+
A quick and dirty test harness for testing assertions before and after
|
5
|
+
an executable is run.
|
6
|
+
|
7
|
+
Synopsis
|
8
|
+
--------
|
9
|
+
|
10
|
+
Spex is a simple language used to define scenarios that model
|
11
|
+
the correct behavior of an executable.
|
12
|
+
|
13
|
+
The description file consists of exactly one `command` line and any
|
14
|
+
number of `scenario` definitions; for example, the following file can
|
15
|
+
be used to verify running `touch /tmp/foo` will create a new file:
|
16
|
+
|
17
|
+
command 'touch /tmp/foo'
|
18
|
+
|
19
|
+
scenario :new, "Creates a file" do
|
20
|
+
assert_creates_file '/tmp/foo'
|
21
|
+
end
|
22
|
+
|
23
|
+
If this was in `scenarios.rb`, you could run this with spex:
|
24
|
+
|
25
|
+
$ spex execute scenarios.rb --scenario new
|
26
|
+
|
27
|
+
If you had named the scenario `default`, the `--scenario` option
|
28
|
+
wouldn't have been necessary, ie:
|
29
|
+
|
30
|
+
scenario :default "Creates a file" do
|
31
|
+
assert_creates_file '/tmp/foo'
|
32
|
+
end
|
33
|
+
|
34
|
+
$ spex execute scenarios.rb
|
35
|
+
|
36
|
+
You'll notice that this should pass the first time and fail on
|
37
|
+
subsequent invocations -- because the `assert_creates` fails in the
|
38
|
+
event a file exists *before* the command is run.
|
39
|
+
|
40
|
+
If you want to see what command and scenarios are defined in a file,
|
41
|
+
use `spex info`, eg:
|
42
|
+
|
43
|
+
$ spex info scenarios.rb
|
44
|
+
|
45
|
+
Commands with arguments
|
46
|
+
-----------------------
|
47
|
+
|
48
|
+
Let's say you had an executable that reads in a configuration file and
|
49
|
+
has some type of side-effect. You'd like to test running the
|
50
|
+
executable against multiple configuration files checking a scenario,
|
51
|
+
without having to edit the spex file every time, changing the path
|
52
|
+
to the configuration file.
|
53
|
+
|
54
|
+
Luckily the command can be provided in `sprintf` style. Assuming our
|
55
|
+
executable is named `myexec` and you pass the configuration file to it
|
56
|
+
via `-c`, the following would work:
|
57
|
+
|
58
|
+
command 'myexec -c %s'
|
59
|
+
|
60
|
+
Now, you just pass more options to `spex execute`:
|
61
|
+
|
62
|
+
$ spex execute scenarios.rb /path/to/my/configuration.conf
|
63
|
+
|
64
|
+
.. and it's just as if you ran:
|
65
|
+
|
66
|
+
$ myexec -c /path/to/my/configuration.conf
|
67
|
+
|
68
|
+
Usage help
|
69
|
+
----------
|
70
|
+
|
71
|
+
See the commandline help documentation:
|
72
|
+
|
73
|
+
$ spex
|
74
|
+
|
75
|
+
For more information on specific commands, you'll want to use `help`,
|
76
|
+
eg:
|
77
|
+
|
78
|
+
$ spex help execute
|
79
|
+
|
80
|
+
Examples
|
81
|
+
--------
|
82
|
+
|
83
|
+
See the `examples/` directory.
|
84
|
+
|
85
|
+
Assertions
|
86
|
+
----------
|
87
|
+
|
88
|
+
The list of assertions is very short at this point.
|
89
|
+
|
90
|
+
To add an assertion, create a class that inherits from
|
91
|
+
`Spex::Assertion` and implements all the neccessary methods. See
|
92
|
+
`Spex::Assertion` and the currently defined assertions for
|
93
|
+
examples.
|
94
|
+
|
95
|
+
Note: If you put your assertions in `~/.spex/assertions/*.rb`,
|
96
|
+
they'll automatically be loaded. If you create any interesting
|
97
|
+
assertions, make sure you let me know!
|
98
|
+
|
99
|
+
### assert_creates_file
|
100
|
+
|
101
|
+
Checks to see if a file was created.
|
102
|
+
|
103
|
+
You can pass `:file => true` or `:directory => true` to ensure the
|
104
|
+
file is a regular file or directory.
|
105
|
+
|
106
|
+
### assert_removes_file
|
107
|
+
|
108
|
+
Checks to see if a file was removed.
|
109
|
+
|
110
|
+
You can pass `:file => true` or `:directory => true` to ensure the
|
111
|
+
file was a regular file or directory before being removed.
|
112
|
+
|
113
|
+
Copyright
|
114
|
+
---------
|
115
|
+
|
116
|
+
Copyright (c) 2010 Bruce Williams. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "spex"
|
8
|
+
gem.summary = %Q{A test harness for executables}
|
9
|
+
gem.description = %Q{An easy-to-use test harness that runs assertions before and after and executable is run}
|
10
|
+
gem.email = "bruce@codefluency.com"
|
11
|
+
gem.homepage = "http://github.com/bruce/spex"
|
12
|
+
gem.authors = ["Bruce Williams"]
|
13
|
+
gem.add_dependency "thor"
|
14
|
+
gem.add_dependency "shoulda"
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rake/testtask'
|
22
|
+
Rake::TestTask.new(:test) do |test|
|
23
|
+
test.libs << 'lib' << 'test'
|
24
|
+
test.pattern = 'test/**/test_*.rb'
|
25
|
+
test.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'rcov/rcovtask'
|
30
|
+
Rcov::RcovTask.new do |test|
|
31
|
+
test.libs << 'test'
|
32
|
+
test.pattern = 'test/**/test_*.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
rescue LoadError
|
36
|
+
task :rcov do
|
37
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
task :test => :check_dependencies
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "spex #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.4.0
|
data/bin/spex
ADDED
data/lib/spex.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
module Spex
|
2
|
+
autoload :Scenario, 'spex/scenario'
|
3
|
+
autoload :Script, 'spex/script'
|
4
|
+
autoload :CLI, 'spex/cli'
|
5
|
+
autoload :DSL, 'spex/dsl'
|
6
|
+
autoload :Assertion, 'spex/assertion'
|
7
|
+
autoload :FileAssertion, 'spex/assertions/file_assertion'
|
8
|
+
autoload :Runner, 'spex/runner'
|
9
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Spex
|
2
|
+
class Assertion
|
3
|
+
extend Enumerable
|
4
|
+
|
5
|
+
def self.each(&block)
|
6
|
+
registry.each(&block)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.[](name)
|
10
|
+
registry[name]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.registry
|
14
|
+
@registry ||= {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.assertion(name)
|
18
|
+
Assertion.registry[name.to_sym] = self
|
19
|
+
end
|
20
|
+
|
21
|
+
def describe_should_at(event)
|
22
|
+
case event
|
23
|
+
when :before
|
24
|
+
raise NotImplementedError, "Assertion does not describe pre-puppet check"
|
25
|
+
when :after
|
26
|
+
raise NotImplementedError, "Assertion does not describe post-puppet check"
|
27
|
+
else
|
28
|
+
raise ArgumentError, "Unknown event #{event.inspect}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def before?
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
def after?
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'assertions', '**/*.rb')) do |path|
|
44
|
+
require path
|
45
|
+
end
|
46
|
+
|
47
|
+
Dir.glob(File.join(ENV['HOME'], '.spex', 'assertions', '**/*.rb')) do |path|
|
48
|
+
require path
|
49
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'etc'
|
2
|
+
|
3
|
+
module Spex
|
4
|
+
class ChgrpsFileAssertion < FileAssertion
|
5
|
+
assertion :chgrps_file
|
6
|
+
|
7
|
+
def before(test_case)
|
8
|
+
if from_groupname
|
9
|
+
test_case.assert File.exist?(@path), "File does not exist at #{@path}"
|
10
|
+
test_case.assert_equal from_groupname, current_groupname
|
11
|
+
elsif @options[:changes]
|
12
|
+
test_case.assert_not_equal to_groupname, current_groupname, "Group will not be changed; already '#{to_groupname}'"
|
13
|
+
end
|
14
|
+
@before_groupname = current_groupname
|
15
|
+
end
|
16
|
+
|
17
|
+
def before?
|
18
|
+
from_groupname || @options[:changes]
|
19
|
+
end
|
20
|
+
|
21
|
+
def after(test_case)
|
22
|
+
if to_groupname
|
23
|
+
test_case.assert File.exist?(@path), "File does not exist at #{@path}"
|
24
|
+
test_case.assert_equal to_groupname, current_groupname
|
25
|
+
elsif @options[:changes]
|
26
|
+
test_case.assert_not_equal @before_groupname, current_groupname, "Group is still '#{@before_groupname}'"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def after?
|
31
|
+
to_groupname || @options[:changes]
|
32
|
+
end
|
33
|
+
|
34
|
+
def describe_should_at(event)
|
35
|
+
case event
|
36
|
+
when :before
|
37
|
+
if from_groupname
|
38
|
+
"change group of file at `#{@path}` from '#{from_groupname}'"
|
39
|
+
elsif @options[:changes]
|
40
|
+
"not have a file at `#{@path}` with group '#{to_groupname}'"
|
41
|
+
end
|
42
|
+
when :after
|
43
|
+
if to_groupname
|
44
|
+
"have changed group of file at `#{@path}` to '#{to_groupname}'"
|
45
|
+
elsif @options[:changes]
|
46
|
+
"have changed group of file at `#{@path}`"
|
47
|
+
end
|
48
|
+
else
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def current_groupname
|
54
|
+
normalize(File.stat(@path).gid)
|
55
|
+
end
|
56
|
+
|
57
|
+
def from_groupname
|
58
|
+
if @options[:from]
|
59
|
+
@from_groupname ||= normalize(@options[:from])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_groupname
|
64
|
+
if @options[:to]
|
65
|
+
@to_groupname ||= normalize(@options[:to])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def normalize(gid_or_groupname)
|
70
|
+
case gid_or_groupname
|
71
|
+
when String, Symbol
|
72
|
+
gid_or_groupname.to_s
|
73
|
+
when Fixnum
|
74
|
+
Etc.getgrgid(gid_or_groupname).name
|
75
|
+
else
|
76
|
+
raise ArgumentError, "Does not appear to be a gid or group name: #{gid_or_groupname}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Spex
|
2
|
+
class ChmodsFileAssertion < FileAssertion
|
3
|
+
assertion :chmods_file
|
4
|
+
|
5
|
+
def before(test_case)
|
6
|
+
if @options[:from]
|
7
|
+
test_case.assert File.exist?(@path), "File does not exist at #{@path}"
|
8
|
+
test_case.assert_equal @options[:from].to_s(8), current_mode.to_s(8)
|
9
|
+
elsif @options[:changes]
|
10
|
+
test_case.assert_not_equal @options[:to].to_s(8), current_mode.to_s(8), "Mode will not be changed; already at mode #{@options[:to].to_s(8)}"
|
11
|
+
end
|
12
|
+
@before_mode = current_mode
|
13
|
+
end
|
14
|
+
|
15
|
+
def before?
|
16
|
+
@options[:from] || @options[:changes]
|
17
|
+
end
|
18
|
+
|
19
|
+
def after(test_case)
|
20
|
+
if @options[:to]
|
21
|
+
test_case.assert File.exist?(@path), "File does not exist at #{@path}"
|
22
|
+
test_case.assert_equal @options[:to].to_s(8), current_mode.to_s(8)
|
23
|
+
elsif @options[:changes]
|
24
|
+
test_case.assert_not_equal @before_mode.to_s(8), current_mode.to_s(8), "Mode is still #{@before_mode.to_s(8)}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def after?
|
29
|
+
@options[:to] || @options[:changes]
|
30
|
+
end
|
31
|
+
|
32
|
+
def describe_should_at(event)
|
33
|
+
case event
|
34
|
+
when :before
|
35
|
+
if @options[:from]
|
36
|
+
"change mode of file at `#{@path}` from #{@options[:from].to_s(8)}"
|
37
|
+
elsif @options[:changes]
|
38
|
+
"not have a file at `#{@path}` with a mode of #{@options[:to].to_s(8)}"
|
39
|
+
end
|
40
|
+
when :after
|
41
|
+
if @options[:to]
|
42
|
+
"change mode of file at `#{@path}` to #{@options[:to].to_s(8)}"
|
43
|
+
elsif @options[:changes]
|
44
|
+
"change mode of file at `#{@path}`"
|
45
|
+
end
|
46
|
+
else
|
47
|
+
super
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def current_mode
|
52
|
+
Integer(File.stat(@path).mode.to_s(8)[1..-1])
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'etc'
|
2
|
+
|
3
|
+
module Spex
|
4
|
+
class ChownsFileAssertion < FileAssertion
|
5
|
+
assertion :chowns_file
|
6
|
+
|
7
|
+
def before(test_case)
|
8
|
+
if from_username
|
9
|
+
test_case.assert File.exist?(@path), "File does not exist at #{@path}"
|
10
|
+
test_case.assert_equal from_username, current_username
|
11
|
+
elsif @options[:changes]
|
12
|
+
test_case.assert_not_equal to_username, current_username, "Owner will not be changed; already '#{to_username}'"
|
13
|
+
end
|
14
|
+
@before_username = current_username
|
15
|
+
end
|
16
|
+
|
17
|
+
def before?
|
18
|
+
from_username || @options[:changes]
|
19
|
+
end
|
20
|
+
|
21
|
+
def after(test_case)
|
22
|
+
if to_username
|
23
|
+
test_case.assert File.exist?(@path), "File does not exist at #{@path}"
|
24
|
+
test_case.assert_equal to_username, current_username
|
25
|
+
elsif @options[:changes]
|
26
|
+
test_case.assert_not_equal @before_username, current_username, "Owner is still '#{@before_username}'"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def after?
|
31
|
+
to_username || @options[:changes]
|
32
|
+
end
|
33
|
+
|
34
|
+
def describe_should_at(event)
|
35
|
+
case event
|
36
|
+
when :before
|
37
|
+
if from_username
|
38
|
+
"change owner of file at `#{@path}` from '#{from_username}'"
|
39
|
+
elsif @options[:changes]
|
40
|
+
"not have a file at `#{@path}` with owner '#{to_username}'"
|
41
|
+
end
|
42
|
+
when :after
|
43
|
+
if to_username
|
44
|
+
"have changed owner of file at `#{@path}` to '#{to_username}'"
|
45
|
+
elsif @options[:changes]
|
46
|
+
"have changed owner of file at `#{@path}`"
|
47
|
+
end
|
48
|
+
else
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def current_username
|
54
|
+
normalize(File.stat(@path).uid)
|
55
|
+
end
|
56
|
+
|
57
|
+
def from_username
|
58
|
+
if @options[:from]
|
59
|
+
@from_username ||= normalize(@options[:from])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_username
|
64
|
+
if @options[:to]
|
65
|
+
@to_username ||= normalize(@options[:to])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def normalize(uid_or_username)
|
70
|
+
case uid_or_username
|
71
|
+
when String, Symbol
|
72
|
+
uid_or_username.to_s
|
73
|
+
when Fixnum
|
74
|
+
Etc.getpwuid(uid_or_username).name
|
75
|
+
else
|
76
|
+
raise ArgumentError, "Does not appear to be a uid or username: #{uid_or_username}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Spex
|
2
|
+
class CreatesFileAssertion < FileAssertion
|
3
|
+
assertion :creates_file
|
4
|
+
|
5
|
+
def before(test_case)
|
6
|
+
test_case.assert !File.exist?(@path), "File already exists at #{@path}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def after(test_case)
|
10
|
+
test_case.assert File.exist?(@path), "File was not created at #{@path}"
|
11
|
+
case kind
|
12
|
+
when :file
|
13
|
+
test_case.assert File.file?(@path), "File created at #{@path} is not a regular file"
|
14
|
+
when :directory
|
15
|
+
test_case.assert File.file?(@path), "File created at #{@path} is not a directory"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def describe_should_at(event)
|
20
|
+
case event
|
21
|
+
when :before
|
22
|
+
"not find #{kind_name} at `#{@path}`"
|
23
|
+
when :after
|
24
|
+
"have created #{kind_name} at `#{@path}`"
|
25
|
+
else
|
26
|
+
super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Spex
|
2
|
+
class FileAssertion < Assertion
|
3
|
+
|
4
|
+
def initialize(path, options = {})
|
5
|
+
@path = path
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def kind
|
10
|
+
if @options[:directory]
|
11
|
+
:directory
|
12
|
+
elsif @options[:file]
|
13
|
+
:file
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def kind_name
|
18
|
+
case kind
|
19
|
+
when :file
|
20
|
+
'a regular file'
|
21
|
+
when :directory
|
22
|
+
'a directory'
|
23
|
+
else
|
24
|
+
'a file'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Spex
|
2
|
+
class RemovesFileAssertion < FileAssertion
|
3
|
+
assertion :removes_file
|
4
|
+
|
5
|
+
def after(test_case)
|
6
|
+
test_case.assert !File.exist?(@path), "File still exists at #{@path}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def before(test_case)
|
10
|
+
test_case.assert File.exist?(@path), "File does not exist at #{@path}"
|
11
|
+
case kind
|
12
|
+
when :file
|
13
|
+
test_case.assert File.file?(@path), "File to remove at #{@path} is not a regular file"
|
14
|
+
when :directory
|
15
|
+
test_case.assert File.file?(@path), "File to remove at #{@path} is not a directory"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def describe_should_at(event)
|
20
|
+
case event
|
21
|
+
when :after
|
22
|
+
"have removed #{kind_name} at `#{@path}`"
|
23
|
+
when :before
|
24
|
+
"find #{kind_name} at `#{@path}` to remove"
|
25
|
+
else
|
26
|
+
super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
data/lib/spex/cli.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
begin
|
2
|
+
require 'thor'
|
3
|
+
rescue LoadError
|
4
|
+
abort "Requires 'thor'"
|
5
|
+
end
|
6
|
+
|
7
|
+
module Spex
|
8
|
+
class CLI < Thor
|
9
|
+
|
10
|
+
desc "info FILE", "Display defined command and scenarios in FILE"
|
11
|
+
def info(path)
|
12
|
+
script = path_to_script(path)
|
13
|
+
puts "FILE\n\n #{path}\n\n"
|
14
|
+
puts "COMMAND\n\n #{script.command}\n\n"
|
15
|
+
puts "SCENARIOS\n\n"
|
16
|
+
script.scenarios.sort_by { |k, v| k.to_s }.each do |_, scenario|
|
17
|
+
puts " #{scenario.name}: #{scenario.description}"
|
18
|
+
[:before, :after].each do |event|
|
19
|
+
puts " #{event.to_s.upcase}"
|
20
|
+
scenario.assertions.each do |assertion|
|
21
|
+
puts " should #{assertion.describe_should_at(event)}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
method_option :scenario, :aliases => '-s', :description => "Scenario to run", :default => 'default'
|
28
|
+
desc "execute FILE [ARGS_FOR_COMMAND]", "Execute a scenario in FILE"
|
29
|
+
def execute(path, *args)
|
30
|
+
script = path_to_script(path)
|
31
|
+
scenario = script[options[:scenario]]
|
32
|
+
unless scenario
|
33
|
+
abort "Could not find scenario: #{options[:scenario]}"
|
34
|
+
end
|
35
|
+
Runner.new(script, scenario, *args).run
|
36
|
+
end
|
37
|
+
|
38
|
+
no_tasks do
|
39
|
+
def path_to_script(path)
|
40
|
+
unless File.exist?(path)
|
41
|
+
abort "No spex file found at #{path}"
|
42
|
+
end
|
43
|
+
script = Script.evaluate_file(path)
|
44
|
+
script.validate!
|
45
|
+
script
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
data/lib/spex/runner.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'shoulda'
|
5
|
+
rescue LoadError
|
6
|
+
abort "Requires 'shoulda'"
|
7
|
+
end
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'test/unit'
|
11
|
+
rescue LoadError
|
12
|
+
abort "Requires 'test/unit'. On Ruby 1.9 you may need to install the 'test-unit' gem."
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'test/unit/ui/console/testrunner'
|
16
|
+
|
17
|
+
module Spex
|
18
|
+
class Runner
|
19
|
+
|
20
|
+
attr_reader :script, :scenario
|
21
|
+
def initialize(script, scenario, *args)
|
22
|
+
@script = script
|
23
|
+
@scenario = scenario
|
24
|
+
@args = args
|
25
|
+
@log = ''
|
26
|
+
end
|
27
|
+
|
28
|
+
def run
|
29
|
+
Test::Unit.run = false
|
30
|
+
suite = Test::Unit::TestSuite.new("#{scenario.description} (`#{command}`)")
|
31
|
+
suite << test(:before).suite
|
32
|
+
suite << test(:after).suite
|
33
|
+
Test::Unit::UI::Console::TestRunner.run(suite)
|
34
|
+
output_log
|
35
|
+
end
|
36
|
+
|
37
|
+
def output_log
|
38
|
+
if @log.empty?
|
39
|
+
puts "NO OUTPUT FROM `#{command}`"
|
40
|
+
else
|
41
|
+
line = "\nOUTPUT FROM `#{command}`"
|
42
|
+
puts line, ('=' * line.size)
|
43
|
+
puts @log
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def run_command
|
48
|
+
Open3.popen3(command) do |stdin, stdout, stderr|
|
49
|
+
stdin.close
|
50
|
+
@log << stdout.read
|
51
|
+
@log << stderr.read
|
52
|
+
end
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
def test(event)
|
57
|
+
klass = Class.new(Test::Unit::TestCase) do
|
58
|
+
class << self; attr_accessor :spex, :name, :event; end
|
59
|
+
end
|
60
|
+
klass.name = "Spex::Test::Order#{event == :after ? 1 : 0}::#{event.to_s.capitalize}Puppet"
|
61
|
+
klass.spex = self
|
62
|
+
klass.event = event
|
63
|
+
klass.context "#{event} `#{command}`" do
|
64
|
+
if parent.event == :after
|
65
|
+
setup do
|
66
|
+
@ran_puppet ||= self.class.spex.run_command
|
67
|
+
end
|
68
|
+
end
|
69
|
+
parent.spex.scenario.assertions.each do |assertion|
|
70
|
+
if assertion.send("#{event}?")
|
71
|
+
should assertion.describe_should_at(event) do
|
72
|
+
assertion.__send__(event, self)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
klass
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def command
|
83
|
+
@command ||= @script.command % @args
|
84
|
+
rescue ArgumentError
|
85
|
+
abort "You provided the wrong number of arguments for command: #{@script.command}"
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Spex
|
2
|
+
class Scenario
|
3
|
+
|
4
|
+
attr_reader :name, :description
|
5
|
+
def initialize(name, description = name.to_s.capitalize, &block)
|
6
|
+
@name = name
|
7
|
+
@description = description
|
8
|
+
instance_eval(&block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def assertions
|
12
|
+
@assertions ||= []
|
13
|
+
end
|
14
|
+
|
15
|
+
Assertion.each do |name, klass|
|
16
|
+
class_eval %{
|
17
|
+
def assert_#{name}(*args, &block)
|
18
|
+
assertions << Assertion[:#{name}].new(*args, &block)
|
19
|
+
end
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
data/lib/spex/script.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module Spex
|
2
|
+
class Script
|
3
|
+
|
4
|
+
attr_accessor :command
|
5
|
+
|
6
|
+
def self.evaluate_file(path)
|
7
|
+
evaluate(File.read(path), path)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.evaluate(text, path)
|
11
|
+
script = new
|
12
|
+
Builder.new(script).instance_eval(text, path, 1)
|
13
|
+
script
|
14
|
+
end
|
15
|
+
|
16
|
+
def <<(scenario)
|
17
|
+
scenarios[scenario.name.to_sym] = scenario
|
18
|
+
end
|
19
|
+
|
20
|
+
def scenarios
|
21
|
+
@scenarios ||= {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def [](name)
|
25
|
+
scenarios[name.to_sym]
|
26
|
+
end
|
27
|
+
|
28
|
+
def validate!
|
29
|
+
unless @command
|
30
|
+
abort "ERROR: The command was not set.\n\nExample:\n\n command 'cat %s'"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Builder
|
35
|
+
|
36
|
+
def initialize(script, &block)
|
37
|
+
@script = script
|
38
|
+
instance_eval(&block) if block_given?
|
39
|
+
end
|
40
|
+
|
41
|
+
def scenario(name, description = name.to_s, &block)
|
42
|
+
scenario = ::Spex::Scenario.new(name, description, &block)
|
43
|
+
@script << scenario
|
44
|
+
end
|
45
|
+
def command(line)
|
46
|
+
@script.command = line
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Spex
|
2
|
+
class Script::Language
|
3
|
+
|
4
|
+
def initialize(script)
|
5
|
+
@script = script
|
6
|
+
end
|
7
|
+
|
8
|
+
def doing(name, description, &block)
|
9
|
+
scenario = Scenario.new(name, description)
|
10
|
+
Scenario::Builder.new(scenario, &block)
|
11
|
+
@script << scenario
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
data/test/helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: spex
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 4
|
8
|
+
- 0
|
9
|
+
version: 0.4.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Bruce Williams
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-04-08 00:00:00 -07:00
|
18
|
+
default_executable: spex
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: thor
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :runtime
|
31
|
+
version_requirements: *id001
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: shoulda
|
34
|
+
prerelease: false
|
35
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
segments:
|
40
|
+
- 0
|
41
|
+
version: "0"
|
42
|
+
type: :runtime
|
43
|
+
version_requirements: *id002
|
44
|
+
description: An easy-to-use test harness that runs assertions before and after and executable is run
|
45
|
+
email: bruce@codefluency.com
|
46
|
+
executables:
|
47
|
+
- spex
|
48
|
+
extensions: []
|
49
|
+
|
50
|
+
extra_rdoc_files:
|
51
|
+
- LICENSE
|
52
|
+
- README.markdown
|
53
|
+
files:
|
54
|
+
- .document
|
55
|
+
- .gitignore
|
56
|
+
- LICENSE
|
57
|
+
- README.markdown
|
58
|
+
- Rakefile
|
59
|
+
- VERSION
|
60
|
+
- bin/spex
|
61
|
+
- examples/chgrp/run.sh
|
62
|
+
- examples/chgrp/scenarios.rb
|
63
|
+
- examples/chmod/run.sh
|
64
|
+
- examples/chmod/scenarios.rb
|
65
|
+
- examples/chown/run.sh
|
66
|
+
- examples/chown/scenarios.rb
|
67
|
+
- examples/puppet/manifest.pp
|
68
|
+
- examples/puppet/run.sh
|
69
|
+
- examples/puppet/scenarios.rb
|
70
|
+
- examples/touch/run.sh
|
71
|
+
- examples/touch/scenarios.rb
|
72
|
+
- lib/spex.rb
|
73
|
+
- lib/spex/assertion.rb
|
74
|
+
- lib/spex/assertions/chgrps_file_assertion.rb
|
75
|
+
- lib/spex/assertions/chmods_file_assertion.rb
|
76
|
+
- lib/spex/assertions/chowns_file_assertion.rb
|
77
|
+
- lib/spex/assertions/creates_file_assertion.rb
|
78
|
+
- lib/spex/assertions/file_assertion.rb
|
79
|
+
- lib/spex/assertions/removes_file_assertion.rb
|
80
|
+
- lib/spex/cli.rb
|
81
|
+
- lib/spex/runner.rb
|
82
|
+
- lib/spex/scenario.rb
|
83
|
+
- lib/spex/script.rb
|
84
|
+
- lib/spex/script/builder.rb
|
85
|
+
- test/helper.rb
|
86
|
+
- test/test_stringup.rb
|
87
|
+
has_rdoc: true
|
88
|
+
homepage: http://github.com/bruce/spex
|
89
|
+
licenses: []
|
90
|
+
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options:
|
93
|
+
- --charset=UTF-8
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
segments:
|
101
|
+
- 0
|
102
|
+
version: "0"
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
segments:
|
108
|
+
- 0
|
109
|
+
version: "0"
|
110
|
+
requirements: []
|
111
|
+
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 1.3.6
|
114
|
+
signing_key:
|
115
|
+
specification_version: 3
|
116
|
+
summary: A test harness for executables
|
117
|
+
test_files:
|
118
|
+
- test/helper.rb
|
119
|
+
- test/test_stringup.rb
|
120
|
+
- examples/chgrp/scenarios.rb
|
121
|
+
- examples/chmod/scenarios.rb
|
122
|
+
- examples/chown/scenarios.rb
|
123
|
+
- examples/puppet/scenarios.rb
|
124
|
+
- examples/touch/scenarios.rb
|