spex 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +66 -0
- data/Rakefile +1 -2
- data/VERSION +1 -1
- data/bin/spex +1 -1
- data/examples/chgrp.rb +5 -0
- data/examples/chmod.rb +5 -0
- data/examples/chown.rb +5 -0
- data/examples/puppet.rb +9 -0
- data/examples/touch.rb +5 -0
- data/examples/writing.rb +5 -0
- data/lib/spex.rb +4 -4
- data/lib/spex/assertion.rb +57 -13
- data/lib/spex/assertions/changed_group_assertion.rb +61 -0
- data/lib/spex/assertions/changed_mode_assertion.rb +48 -0
- data/lib/spex/assertions/changed_owner_assertion.rb +73 -0
- data/lib/spex/assertions/created_assertion.rb +26 -0
- data/lib/spex/assertions/file_assertion.rb +1 -10
- data/lib/spex/assertions/modified_assertion.rb +56 -0
- data/lib/spex/assertions/removed_assertion.rb +27 -0
- data/lib/spex/cli.rb +65 -36
- data/lib/spex/execution.rb +37 -0
- data/lib/spex/runner.rb +73 -51
- data/lib/spex/scenario.rb +22 -12
- data/lib/spex/script.rb +13 -18
- data/test/helper.rb +7 -0
- data/test/test_assertion.rb +58 -0
- data/test/test_script.rb +54 -0
- metadata +29 -42
- data/README.markdown +0 -116
- data/examples/chgrp/run.sh +0 -3
- data/examples/chgrp/scenarios.rb +0 -5
- data/examples/chmod/run.sh +0 -3
- data/examples/chmod/scenarios.rb +0 -5
- data/examples/chown/run.sh +0 -3
- data/examples/chown/scenarios.rb +0 -5
- data/examples/puppet/manifest.pp +0 -3
- data/examples/puppet/run.sh +0 -3
- data/examples/puppet/scenarios.rb +0 -5
- data/examples/touch/run.sh +0 -3
- data/examples/touch/scenarios.rb +0 -5
- data/lib/spex/assertions/chgrps_file_assertion.rb +0 -81
- data/lib/spex/assertions/chmods_file_assertion.rb +0 -56
- data/lib/spex/assertions/chowns_file_assertion.rb +0 -81
- data/lib/spex/assertions/creates_file_assertion.rb +0 -31
- data/lib/spex/assertions/removes_file_assertion.rb +0 -31
- data/lib/spex/script/builder.rb +0 -15
- data/test/test_stringup.rb +0 -7
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
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
|
+
scenario "Creates a file" do
|
18
|
+
executing 'touch /tmp/foo' do
|
19
|
+
assert '/tmp/foo', :created => true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
If this was in `run_touch.rb`, you could run this with spex:
|
24
|
+
|
25
|
+
$ spex run_touch.rb
|
26
|
+
|
27
|
+
You'll notice that this should pass the first time and fail on
|
28
|
+
subsequent invocations -- because the assertion added by `:created => true` fails in the
|
29
|
+
event a file exists *before* the command is run.
|
30
|
+
|
31
|
+
If you want to see what command and scenarios are defined in a file,
|
32
|
+
use `spex info`, eg:
|
33
|
+
|
34
|
+
$ spex --describe run_touch.rb
|
35
|
+
|
36
|
+
Usage help
|
37
|
+
----------
|
38
|
+
|
39
|
+
See the commandline help documentation:
|
40
|
+
|
41
|
+
$ spex --help
|
42
|
+
|
43
|
+
Examples
|
44
|
+
--------
|
45
|
+
|
46
|
+
See the `examples/` directory.
|
47
|
+
|
48
|
+
Assertions
|
49
|
+
----------
|
50
|
+
|
51
|
+
See the [wiki](http://wiki.github.com/bruce/spex/supported-assertions)
|
52
|
+
for the list of supported assertions.
|
53
|
+
|
54
|
+
To add an assertion, create a class that inherits from
|
55
|
+
`Spex::Assertion` and implements all the neccessary methods. See
|
56
|
+
`Spex::Assertion` and the currently defined assertions for
|
57
|
+
examples.
|
58
|
+
|
59
|
+
Note: If you put your assertions in `~/.spex/assertions/*.rb`,
|
60
|
+
they'll automatically be loaded. If you create any interesting
|
61
|
+
assertions, add them to the [wiki](http://wiki.github.com/bruce/spex/community-assertions)!
|
62
|
+
|
63
|
+
Copyright
|
64
|
+
---------
|
65
|
+
|
66
|
+
Copyright (c) 2010 Bruce Williams. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -10,8 +10,7 @@ begin
|
|
10
10
|
gem.email = "bruce@codefluency.com"
|
11
11
|
gem.homepage = "http://github.com/bruce/spex"
|
12
12
|
gem.authors = ["Bruce Williams"]
|
13
|
-
gem.add_dependency "
|
14
|
-
gem.add_dependency "shoulda"
|
13
|
+
gem.add_dependency "colored"
|
15
14
|
end
|
16
15
|
Jeweler::GemcutterTasks.new
|
17
16
|
rescue LoadError
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.5.0
|
data/bin/spex
CHANGED
data/examples/chgrp.rb
ADDED
data/examples/chmod.rb
ADDED
data/examples/chown.rb
ADDED
data/examples/puppet.rb
ADDED
data/examples/touch.rb
ADDED
data/examples/writing.rb
ADDED
data/lib/spex.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
module Spex
|
2
|
-
autoload :Scenario, 'spex/scenario'
|
3
|
-
autoload :Script, 'spex/script'
|
4
|
-
autoload :CLI, 'spex/cli'
|
5
|
-
autoload :DSL, 'spex/dsl'
|
6
2
|
autoload :Assertion, 'spex/assertion'
|
3
|
+
autoload :CLI, 'spex/cli'
|
4
|
+
autoload :Execution, 'spex/execution'
|
7
5
|
autoload :FileAssertion, 'spex/assertions/file_assertion'
|
8
6
|
autoload :Runner, 'spex/runner'
|
7
|
+
autoload :Scenario, 'spex/scenario'
|
8
|
+
autoload :Script, 'spex/script'
|
9
9
|
end
|
data/lib/spex/assertion.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
|
+
require 'test/unit/assertions'
|
2
|
+
|
1
3
|
module Spex
|
2
4
|
class Assertion
|
5
|
+
include Test::Unit::Assertions
|
3
6
|
extend Enumerable
|
7
|
+
|
8
|
+
class UnknownOptionError < ::ArgumentError; end
|
4
9
|
|
5
10
|
def self.each(&block)
|
6
11
|
registry.each(&block)
|
@@ -13,30 +18,69 @@ module Spex
|
|
13
18
|
def self.registry
|
14
19
|
@registry ||= {}
|
15
20
|
end
|
16
|
-
|
17
|
-
def self.
|
21
|
+
|
22
|
+
def self.as(name, description)
|
23
|
+
self.name = name
|
24
|
+
self.description = description
|
18
25
|
Assertion.registry[name.to_sym] = self
|
19
26
|
end
|
20
27
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
28
|
+
def self.options
|
29
|
+
@options ||= {}
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.option(name, description = nil, &block)
|
33
|
+
options[name] = Option.new(name, description, &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
class << self; attr_accessor :name, :description; end
|
37
|
+
|
38
|
+
attr_reader :target, :options
|
39
|
+
def initialize(target, options = {})
|
40
|
+
@target = target
|
41
|
+
if options.is_a?(Hash)
|
42
|
+
@options = options
|
43
|
+
@active = true
|
27
44
|
else
|
28
|
-
|
45
|
+
@options = {}
|
46
|
+
@active = options
|
47
|
+
end
|
48
|
+
validate!
|
49
|
+
end
|
50
|
+
|
51
|
+
def validate!
|
52
|
+
options.each do |key, value|
|
53
|
+
option = self.class.options[key]
|
54
|
+
unless option
|
55
|
+
raise UnknownOptionError, key.to_s
|
56
|
+
end
|
29
57
|
end
|
30
58
|
end
|
31
59
|
|
32
|
-
def
|
33
|
-
|
60
|
+
def active?
|
61
|
+
@active
|
62
|
+
end
|
63
|
+
|
64
|
+
def prepare
|
65
|
+
end
|
66
|
+
|
67
|
+
def before
|
68
|
+
end
|
69
|
+
|
70
|
+
def after
|
34
71
|
end
|
35
72
|
|
36
|
-
def
|
37
|
-
|
73
|
+
def to_s
|
74
|
+
"#{self.class.description} of #{target}"
|
38
75
|
end
|
39
76
|
|
77
|
+
class Option
|
78
|
+
attr_reader :name, :description
|
79
|
+
def initialize(name, description = nil, &block)
|
80
|
+
@name = name
|
81
|
+
@description = description
|
82
|
+
end
|
83
|
+
end
|
40
84
|
end
|
41
85
|
end
|
42
86
|
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'etc'
|
2
|
+
|
3
|
+
module Spex
|
4
|
+
class ChangedGroupAssertion < FileAssertion
|
5
|
+
as :changed_group, 'file group change'
|
6
|
+
option :to, "To group name or gid"
|
7
|
+
option :from, "From group name or gid"
|
8
|
+
|
9
|
+
def before
|
10
|
+
assert File.exist?(target), "File does not exist at #{target}"
|
11
|
+
if from_groupname
|
12
|
+
assert_equal from_groupname, current_groupname
|
13
|
+
elsif active?
|
14
|
+
assert_not_equal to_groupname, current_groupname, "Group will not be changed; already '#{to_groupname}'"
|
15
|
+
end
|
16
|
+
@before_groupname = current_groupname
|
17
|
+
end
|
18
|
+
|
19
|
+
def after
|
20
|
+
assert File.exist?(target), "File does not exist at #{target}"
|
21
|
+
current = current_groupname
|
22
|
+
if to_groupname
|
23
|
+
assert_equal to_groupname, current
|
24
|
+
elsif active?
|
25
|
+
assert_not_equal @before_groupname, current, "Group is still '#{@before_groupname}'"
|
26
|
+
elsif !active?
|
27
|
+
assert_equal @before_groupname, current, "Group changed from '#{@before_groupname}' to '#{current}'"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def current_groupname
|
34
|
+
normalize(File.stat(target).gid)
|
35
|
+
end
|
36
|
+
|
37
|
+
def from_groupname
|
38
|
+
if options[:from]
|
39
|
+
@from_groupname ||= normalize(options[:from])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_groupname
|
44
|
+
if options[:to]
|
45
|
+
@to_groupname ||= normalize(options[:to])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def normalize(gid_or_groupname)
|
50
|
+
case gid_or_groupname
|
51
|
+
when String, Symbol
|
52
|
+
gid_or_groupname.to_s
|
53
|
+
when Fixnum
|
54
|
+
Etc.getgrgid(gid_or_groupname).name
|
55
|
+
else
|
56
|
+
raise ArgumentError, "Does not appear to be a gid or group name: #{gid_or_groupname}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Spex
|
2
|
+
class ChangedModeAssertion < FileAssertion
|
3
|
+
as :changed_mode, "file mode change"
|
4
|
+
option :from, "Mode changed from (octal, eg 0600)"
|
5
|
+
option :to, "Mode changed to (octal, eg 0700)"
|
6
|
+
|
7
|
+
def before
|
8
|
+
assert File.exist?(target), "File does not exist at #{target}"
|
9
|
+
if options[:from]
|
10
|
+
assert_equal options[:from].to_s(8), current_mode.to_s(8)
|
11
|
+
elsif options[:to]
|
12
|
+
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)}"
|
13
|
+
end
|
14
|
+
@before_mode = current_mode
|
15
|
+
end
|
16
|
+
|
17
|
+
def after
|
18
|
+
assert File.exist?(target), "File does not exist at #{target}"
|
19
|
+
mode = current_mode.to_s(8)
|
20
|
+
if options[:to]
|
21
|
+
assert_equal options[:to].to_s(8), mode
|
22
|
+
elsif active?
|
23
|
+
assert_not_equal @before_mode.to_s(8), mode, "Mode is still #{@before_mode.to_s(8)}"
|
24
|
+
elsif !active?
|
25
|
+
assert_equal @before_mode.to_s(8), mode, "Mode was changed from #{@before_mode.to_s(8)} to #{mode}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
[super, detail].join(' ')
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def detail
|
36
|
+
[:from, :to].map do |where|
|
37
|
+
if options[where]
|
38
|
+
"#{where} %o" % options[where]
|
39
|
+
end
|
40
|
+
end.compact.join(' ')
|
41
|
+
end
|
42
|
+
|
43
|
+
def current_mode
|
44
|
+
Integer(File.stat(target).mode.to_s(8)[1..-1])
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'etc'
|
2
|
+
|
3
|
+
module Spex
|
4
|
+
class ChangedOwnerAssertion < FileAssertion
|
5
|
+
as :changed_owner, 'file owner change'
|
6
|
+
option :from, "From username or uid"
|
7
|
+
option :to, "To username or uid"
|
8
|
+
|
9
|
+
def before
|
10
|
+
assert File.exist?(target), "File does not exist at #{target}"
|
11
|
+
if from_username
|
12
|
+
assert_equal from_username, current_username, "File is not owned by '#{from_username}'"
|
13
|
+
elsif to_username
|
14
|
+
assert_not_equal to_username, current_username, "Owner will not be changed; already '#{to_username}'"
|
15
|
+
end
|
16
|
+
@before_username = current_username
|
17
|
+
end
|
18
|
+
|
19
|
+
def after
|
20
|
+
current = current_username
|
21
|
+
if to_username
|
22
|
+
assert File.exist?(target), "File does not exist at #{target}"
|
23
|
+
assert_equal to_username, current
|
24
|
+
elsif active?
|
25
|
+
assert_not_equal @before_username, current, "Owner is still '#{@before_username}'"
|
26
|
+
elsif !active?
|
27
|
+
assert_equal @before_username, current, "Owner was changed from '#{@before_username}' to '#{current}'"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
[super, detail].join(' ')
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def detail
|
38
|
+
[:from, :to].map do |where|
|
39
|
+
if options[where]
|
40
|
+
"#{where} %s" % normalize(options[where])
|
41
|
+
end
|
42
|
+
end.compact.join(' ')
|
43
|
+
end
|
44
|
+
|
45
|
+
def current_username
|
46
|
+
normalize(File.stat(target).uid)
|
47
|
+
end
|
48
|
+
|
49
|
+
def from_username
|
50
|
+
if options[:from]
|
51
|
+
@from_username ||= normalize(options[:from])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_username
|
56
|
+
if options[:to]
|
57
|
+
@to_username ||= normalize(options[:to])
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def normalize(uid_or_username)
|
62
|
+
case uid_or_username
|
63
|
+
when String, Symbol
|
64
|
+
uid_or_username.to_s
|
65
|
+
when Fixnum
|
66
|
+
Etc.getpwuid(uid_or_username).name
|
67
|
+
else
|
68
|
+
raise ArgumentError, "Does not appear to be a uid or username: #{uid_or_username}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Spex
|
2
|
+
class CreatedAssertion < FileAssertion
|
3
|
+
as :created, 'file creation'
|
4
|
+
option :type, "Type ('file' or 'directory'), optional"
|
5
|
+
|
6
|
+
def before
|
7
|
+
assert !File.exist?(target), "File already exists at #{target}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def after
|
11
|
+
assert File.exist?(target), "File was not created at #{target}"
|
12
|
+
check_type
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def check_type
|
18
|
+
case kind
|
19
|
+
when :file
|
20
|
+
assert File.file?(target), "File created at #{target} is not a regular file"
|
21
|
+
when :directory
|
22
|
+
assert File.directory?(target), "File created at #{target} is not a directory"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|