spex 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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