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