spex 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -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
|
-
|
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
|
-
|
2
|
-
|
3
|
-
rescue LoadError
|
4
|
-
abort "Requires 'thor'"
|
5
|
-
end
|
1
|
+
require 'optparse'
|
2
|
+
require 'ostruct'
|
6
3
|
|
7
4
|
module Spex
|
8
|
-
class CLI
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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 '
|
3
|
+
require 'colored'
|
11
4
|
rescue LoadError
|
12
|
-
abort "
|
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
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
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
|
69
|
+
puts content
|
44
70
|
end
|
45
71
|
end
|
46
72
|
|
47
|
-
def
|
48
|
-
|
73
|
+
def execute(execution)
|
74
|
+
log = {:stdout => '', :stderr => ''}
|
75
|
+
Open3.popen3(execution.command) do |stdin, stdout, stderr|
|
49
76
|
stdin.close
|
50
|
-
|
51
|
-
|
77
|
+
log[:stdout] << stdout.read
|
78
|
+
log[:stderr] << stderr.read
|
52
79
|
end
|
53
|
-
|
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 :
|
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}
|
61
|
-
klass.
|
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
|
-
|
91
|
+
klass.context "#{event} executing `#{execution.command}`" do
|
92
|
+
case parent.event
|
93
|
+
when :after
|
65
94
|
setup do
|
66
|
-
@
|
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
|
-
|
70
|
-
|
71
|
-
|
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
|
5
|
-
def initialize(name,
|
5
|
+
attr_reader :name
|
6
|
+
def initialize(name, &block)
|
6
7
|
@name = name
|
7
|
-
|
8
|
-
instance_eval(&block)
|
8
|
+
Builder.new(self, &block)
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
12
|
-
@
|
11
|
+
def executions
|
12
|
+
@executions ||= []
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
|
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
|