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
@@ -1,17 +1,8 @@
1
1
  module Spex
2
2
  class FileAssertion < Assertion
3
3
 
4
- def initialize(path, options = {})
5
- @path = path
6
- @options = options
7
- end
8
-
9
4
  def kind
10
- if @options[:directory]
11
- :directory
12
- elsif @options[:file]
13
- :file
14
- end
5
+ options[:type] ? options[:type].to_sym : :any
15
6
  end
16
7
 
17
8
  def kind_name
@@ -0,0 +1,56 @@
1
+ require 'digest/md5'
2
+
3
+ module Spex
4
+
5
+ # With no option, just verifies a change occurs
6
+ class ModifiedAssertion < FileAssertion
7
+ as :modified, 'file modification'
8
+
9
+ def prepare
10
+ track_checksum!
11
+ end
12
+
13
+ def before
14
+ assert File.exist?(target), "File does not exist at '#{target}'"
15
+ end
16
+
17
+ def after
18
+ assert File.exist?(target), "File does not exist at '#{target}'"
19
+ checksum = current_checksum
20
+ if active?
21
+ assert_not_equal @before_checksum, checksum, "Checksum did not change"
22
+ else
23
+ assert_equal @before_checksum, checksum, "Checksum changed"
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def track_checksum!
30
+ @before_checksum = current_checksum
31
+ end
32
+
33
+ def current_checksum
34
+ if File.exist?(target)
35
+ generate_checksum
36
+ else
37
+ nil
38
+ end
39
+ end
40
+
41
+ def generate_checksum
42
+ digest = Digest::MD5.new
43
+ File.open(target) do |file|
44
+ while content = file.read(4096)
45
+ digest << content
46
+ end
47
+ end
48
+ digest.hexdigest
49
+ end
50
+
51
+ def same_checksum?
52
+ @before_checksum == current_checksum
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,27 @@
1
+ module Spex
2
+ class RemovesFileAssertion < FileAssertion
3
+ as :removes, 'file removal'
4
+ option :mode, "Mode, in octal (eg: 0600), optional"
5
+ option :type, "Type (:file or :directory), optional"
6
+
7
+ def before
8
+ assert File.exist?(target), "File does not exist at #{target}"
9
+ check_type
10
+ end
11
+
12
+ def after
13
+ assert !File.exist?(target), "File still exists at #{target}"
14
+ end
15
+
16
+ private
17
+
18
+ def check_type
19
+ case kind
20
+ when :file
21
+ assert File.file?(target), "File to remove at #{target} is not a regular file"
22
+ when :directory
23
+ assert File.file?(target), "File to remove at #{target} is not a directory"
24
+ end
25
+ end
26
+ end
27
+ end
data/lib/spex/cli.rb CHANGED
@@ -1,50 +1,79 @@
1
- begin
2
- require 'thor'
3
- rescue LoadError
4
- abort "Requires 'thor'"
5
- end
1
+ require 'optparse'
2
+ require 'ostruct'
6
3
 
7
4
  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
5
+ class CLI
6
+
7
+ def initialize(args = [])
8
+ @args = args
9
+ end
10
+
11
+ def options
12
+ @options ||= OpenStruct.new
13
+ end
14
+
15
+ def parser
16
+ @parser ||= OptionParser.new do |opts|
17
+ opts.banner = 'spex DEFINITION_FILE [OPTIONS]'
18
+ opts.separator "\nOPTIONS:"
19
+ opts.on_tail('--help', '-h', 'Show this message') do
20
+ puts opts
21
+ exit
22
+ end
23
+ opts.on('--describe', '-d', 'Describe DEFINITION_FILE') do
24
+ options.describe = true
23
25
  end
24
26
  end
25
27
  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]}"
28
+
29
+ def run
30
+ parser.parse!(@args)
31
+ filename = @args.shift
32
+ if !filename
33
+ refuse "No definition file given"
34
+ elsif !File.exist?(filename)
35
+ refuse "Definition file not found: #{filename}"
36
+ else
37
+ accept(filename)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def accept(filename)
44
+ script = evaluate(filename)
45
+ if options.describe
46
+ describe(script)
47
+ else
48
+ execute(script)
34
49
  end
35
- Runner.new(script, scenario, *args).run
36
50
  end
37
51
 
38
- no_tasks do
39
- def path_to_script(path)
40
- unless File.exist?(path)
41
- abort "No spex file found at #{path}"
52
+ def describe(script)
53
+ script.scenarios.each do |scenario|
54
+ puts %(In scenario "#{scenario.name}")
55
+ scenario.executions.each do |execution|
56
+ puts " When executing `#{execution.command}`"
57
+ execution.assertions.each do |assertion|
58
+ puts " assert #{assertion}"
59
+ end
42
60
  end
43
- script = Script.evaluate_file(path)
44
- script.validate!
45
- script
46
61
  end
47
62
  end
63
+
64
+ def execute(script)
65
+ script.each do |scenario|
66
+ Runner.new(script, scenario).run
67
+ end
68
+ end
69
+
70
+ def refuse(error)
71
+ abort "ERROR: #{error}\n\n#{parser}"
72
+ end
73
+
74
+ def evaluate(filename)
75
+ Script.evaluate_file(filename)
76
+ end
48
77
 
49
78
  end
50
79
  end
@@ -0,0 +1,37 @@
1
+ module Spex
2
+ class Execution
3
+ include Enumerable
4
+
5
+ attr_reader :command
6
+
7
+ def initialize(command, &block)
8
+ @command = command
9
+ Builder.new(self, &block)
10
+ end
11
+
12
+ def assertions
13
+ @assertions ||= []
14
+ end
15
+
16
+ def <<(assertion)
17
+ assertions << assertion
18
+ end
19
+
20
+ def each(&block)
21
+ assertions.each(&block)
22
+ end
23
+
24
+ class Builder
25
+ def initialize(execution, &block)
26
+ @execution = execution
27
+ instance_eval(&block) if block_given?
28
+ end
29
+
30
+ def assert(target, assertion_names = {})
31
+ assertion_names.each do |name, options|
32
+ @execution << Assertion[name].new(target, options)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
data/lib/spex/runner.rb CHANGED
@@ -1,89 +1,111 @@
1
1
  require 'open3'
2
-
3
- begin
4
- require 'shoulda'
5
- rescue LoadError
6
- abort "Requires 'shoulda'"
7
- end
8
-
9
2
  begin
10
- require 'test/unit'
3
+ require 'colored'
11
4
  rescue LoadError
12
- abort "Requires 'test/unit'. On Ruby 1.9 you may need to install the 'test-unit' gem."
5
+ abort "Required the 'colored' library"
13
6
  end
14
7
 
15
- require 'test/unit/ui/console/testrunner'
16
-
17
8
  module Spex
18
9
  class Runner
19
10
 
20
11
  attr_reader :script, :scenario
21
- def initialize(script, scenario, *args)
12
+ def initialize(script, scenario)
22
13
  @script = script
23
14
  @scenario = scenario
24
- @args = args
25
- @log = ''
26
15
  end
27
16
 
28
17
  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
18
+ puts %(Running scenario "#{scenario.name}").bold
19
+ proceed = true
20
+ scenario.each do |execution|
21
+ puts "Checking pre-assertions"
22
+ execution.assertions.each do |assertion|
23
+ print "Pre-assertions for #{assertion}: "
24
+ assertion.prepare
25
+ proceed = report { assertion.before }
26
+ break unless proceed
27
+ end
28
+ if proceed
29
+ puts %(Executing "#{execution.command}")
30
+ log = execute(execution)
31
+ passed = true
32
+ execution.assertions.reverse.each do |assertion|
33
+ print "Post-assertions for #{assertion}: "
34
+ passed = report { assertion.after }
35
+ break unless passed
36
+ end
37
+ if passed
38
+ puts "SCENARIO PASSED".green.bold
39
+ else
40
+ abort "SCENARIO FAILED".red.bold
41
+ end
42
+ output_log(execution, log)
43
+ else
44
+ abort "SCENARIO FAILED (EXECUTION ABORTED)".red.bold
45
+ end
46
+ end
47
+ end
48
+
49
+ def report(&block)
50
+ yield
51
+ puts 'PASSED'.green
52
+ true
53
+ rescue Test::Unit::AssertionFailedError => e
54
+ puts 'FAILED'.red
55
+ puts e.message.yellow
56
+ puts "At #{find_source(e)}".yellow
57
+ false
58
+ end
59
+
60
+ def find_source(e)
61
+ e.backtrace.detect { |line| !line.include?('test/unit') }
35
62
  end
36
63
 
37
- def output_log
38
- if @log.empty?
39
- puts "NO OUTPUT FROM `#{command}`"
40
- else
41
- line = "\nOUTPUT FROM `#{command}`"
64
+ def output_log(execution, log)
65
+ log.each do |stream, content|
66
+ next if content.empty?
67
+ line = "\n#{stream.to_s.upcase} FOR `#{execution.command}`"
42
68
  puts line, ('=' * line.size)
43
- puts @log
69
+ puts content
44
70
  end
45
71
  end
46
72
 
47
- def run_command
48
- Open3.popen3(command) do |stdin, stdout, stderr|
73
+ def execute(execution)
74
+ log = {:stdout => '', :stderr => ''}
75
+ Open3.popen3(execution.command) do |stdin, stdout, stderr|
49
76
  stdin.close
50
- @log << stdout.read
51
- @log << stderr.read
77
+ log[:stdout] << stdout.read
78
+ log[:stderr] << stderr.read
52
79
  end
53
- true
80
+ log
54
81
  end
55
82
 
56
- def test(event)
83
+ def test(execution, event)
57
84
  klass = Class.new(Test::Unit::TestCase) do
58
- class << self; attr_accessor :spex, :name, :event; end
85
+ class << self; attr_accessor :execution, :spex_runner, :name, :event; end
59
86
  end
60
- klass.name = "Spex::Test::Order#{event == :after ? 1 : 0}::#{event.to_s.capitalize}Puppet"
61
- klass.spex = self
87
+ klass.name = "Spex::Test::Order#{event == :after ? 1 : 0}::#{event.to_s.capitalize}Execution"
88
+ klass.spex_runner = self
89
+ klass.execution = execution
62
90
  klass.event = event
63
- klass.context "#{event} `#{command}`" do
64
- if parent.event == :after
91
+ klass.context "#{event} executing `#{execution.command}`" do
92
+ case parent.event
93
+ when :after
65
94
  setup do
66
- @ran_puppet ||= self.class.spex.run_command
95
+ @executed ||= self.class.spex_runner.execute(self.class.execution)
67
96
  end
97
+ order = parent.execution.assertions.reverse
98
+ when :before
99
+ order = parent.execution.assertions
68
100
  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
101
+ order.each do |assertion|
102
+ should "pass assertion #{assertion.inspect}" do
103
+ assertion.__send__(event, self)
74
104
  end
75
105
  end
76
106
  end
77
107
  klass
78
108
  end
79
109
 
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
110
  end
89
111
  end
data/lib/spex/scenario.rb CHANGED
@@ -1,24 +1,34 @@
1
1
  module Spex
2
2
  class Scenario
3
+ include Enumerable
3
4
 
4
- attr_reader :name, :description
5
- def initialize(name, description = name.to_s.capitalize, &block)
5
+ attr_reader :name
6
+ def initialize(name, &block)
6
7
  @name = name
7
- @description = description
8
- instance_eval(&block)
8
+ Builder.new(self, &block)
9
9
  end
10
10
 
11
- def assertions
12
- @assertions ||= []
11
+ def executions
12
+ @executions ||= []
13
13
  end
14
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
- }
15
+ def <<(execution)
16
+ executions << execution
21
17
  end
22
18
 
19
+ def each(&block)
20
+ executions.each(&block)
21
+ end
22
+
23
+ class Builder
24
+ def initialize(scenario, &block)
25
+ @scenario = scenario
26
+ instance_eval(&block) if block_given?
27
+ end
28
+
29
+ def executing(command, &block)
30
+ @scenario << Execution.new(command, &block)
31
+ end
32
+ end
23
33
  end
24
34
  end