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
@@ -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