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.
Files changed (47) hide show
  1. data/README.md +66 -0
  2. data/Rakefile +1 -2
  3. data/VERSION +1 -1
  4. data/bin/spex +1 -1
  5. data/examples/chgrp.rb +5 -0
  6. data/examples/chmod.rb +5 -0
  7. data/examples/chown.rb +5 -0
  8. data/examples/puppet.rb +9 -0
  9. data/examples/touch.rb +5 -0
  10. data/examples/writing.rb +5 -0
  11. data/lib/spex.rb +4 -4
  12. data/lib/spex/assertion.rb +57 -13
  13. data/lib/spex/assertions/changed_group_assertion.rb +61 -0
  14. data/lib/spex/assertions/changed_mode_assertion.rb +48 -0
  15. data/lib/spex/assertions/changed_owner_assertion.rb +73 -0
  16. data/lib/spex/assertions/created_assertion.rb +26 -0
  17. data/lib/spex/assertions/file_assertion.rb +1 -10
  18. data/lib/spex/assertions/modified_assertion.rb +56 -0
  19. data/lib/spex/assertions/removed_assertion.rb +27 -0
  20. data/lib/spex/cli.rb +65 -36
  21. data/lib/spex/execution.rb +37 -0
  22. data/lib/spex/runner.rb +73 -51
  23. data/lib/spex/scenario.rb +22 -12
  24. data/lib/spex/script.rb +13 -18
  25. data/test/helper.rb +7 -0
  26. data/test/test_assertion.rb +58 -0
  27. data/test/test_script.rb +54 -0
  28. metadata +29 -42
  29. data/README.markdown +0 -116
  30. data/examples/chgrp/run.sh +0 -3
  31. data/examples/chgrp/scenarios.rb +0 -5
  32. data/examples/chmod/run.sh +0 -3
  33. data/examples/chmod/scenarios.rb +0 -5
  34. data/examples/chown/run.sh +0 -3
  35. data/examples/chown/scenarios.rb +0 -5
  36. data/examples/puppet/manifest.pp +0 -3
  37. data/examples/puppet/run.sh +0 -3
  38. data/examples/puppet/scenarios.rb +0 -5
  39. data/examples/touch/run.sh +0 -3
  40. data/examples/touch/scenarios.rb +0 -5
  41. data/lib/spex/assertions/chgrps_file_assertion.rb +0 -81
  42. data/lib/spex/assertions/chmods_file_assertion.rb +0 -56
  43. data/lib/spex/assertions/chowns_file_assertion.rb +0 -81
  44. data/lib/spex/assertions/creates_file_assertion.rb +0 -31
  45. data/lib/spex/assertions/removes_file_assertion.rb +0 -31
  46. data/lib/spex/script/builder.rb +0 -15
  47. 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 "thor"
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.4.0
1
+ 0.5.0
data/bin/spex CHANGED
@@ -4,4 +4,4 @@ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
4
 
5
5
  require 'spex'
6
6
 
7
- Spex::CLI.start
7
+ Spex::CLI.new(ARGV).run
data/examples/chgrp.rb ADDED
@@ -0,0 +1,5 @@
1
+ scenario "Change group" do
2
+ executing "sudo chgrp everyone /tmp/foo" do
3
+ assert '/tmp/foo', :changed_group => {:to => 'everyone'}
4
+ end
5
+ end
data/examples/chmod.rb ADDED
@@ -0,0 +1,5 @@
1
+ scenario "Change mode" do
2
+ executing "chmod 700 /tmp/foo" do
3
+ assert '/tmp/foo', :changed_mode => {:to => 0700}
4
+ end
5
+ end
data/examples/chown.rb ADDED
@@ -0,0 +1,5 @@
1
+ scenario "Change owner" do
2
+ executing "sudo chown root /tmp/foo" do
3
+ assert '/tmp/foo', :changed_owner => {:to => 'root'}
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ File.open('/tmp/spex-manifest.pp', 'w') do |f|
2
+ f.puts %(file { "/tmp/foo": ensure => present })
3
+ end
4
+
5
+ scenario "Creates a file" do
6
+ executing "puppet /tmp/spex-manifest.pp" do
7
+ assert '/tmp/foo', :created => true
8
+ end
9
+ end
data/examples/touch.rb ADDED
@@ -0,0 +1,5 @@
1
+ scenario "Creates a file" do
2
+ executing "touch /tmp/foo" do
3
+ assert '/tmp/foo', :created => true
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ scenario "Modifies a file" do
2
+ executing "echo 'foo' >> /tmp/foo" do
3
+ assert '/tmp/foo', :modified => true
4
+ end
5
+ end
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
@@ -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.assertion(name)
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 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"
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
- raise ArgumentError, "Unknown event #{event.inspect}"
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 before?
33
- true
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 after?
37
- true
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