spex 0.4.0 → 0.5.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/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
|