Exspec 1.0.1
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/bin/exspec +34 -0
- data/lib/exspec/context_manager.rb +75 -0
- data/lib/exspec/execute_callbacks.rb +24 -0
- data/lib/exspec/executor.rb +227 -0
- data/lib/exspec/extensions/extension.rb +70 -0
- data/lib/exspec/extensions/mocking.rb +125 -0
- data/lib/exspec/extensions/rails.rb +78 -0
- data/lib/exspec/helpers.rb +10 -0
- data/lib/exspec/irb/irb_context_manager.rb +35 -0
- data/lib/exspec/irb/irb_exspec.rb +40 -0
- data/lib/exspec/irb/irb_patch.rb +51 -0
- data/lib/exspec/logger.rb +71 -0
- data/lib/exspec/regression_test_reporter.rb +93 -0
- data/lib/exspec/reporter.rb +29 -0
- data/lib/exspec/spec.rb +107 -0
- data/lib/exspec/spec_manager.rb +93 -0
- data/lib/exspec/spec_runner.rb +55 -0
- data/lib/exspec.rb +217 -0
- metadata +80 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative "../../exspec"
|
2
|
+
require_relative "irb_context_manager"
|
3
|
+
require_relative "irb_patch"
|
4
|
+
|
5
|
+
module Exspec
|
6
|
+
class IrbExspec < Exspec
|
7
|
+
def initialize(context, workspace)
|
8
|
+
@irb_context = context
|
9
|
+
@irb_workspace = workspace
|
10
|
+
@irb_context_manager = IrbContextManager.new self
|
11
|
+
super :context_manager => @irb_context_manager
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :irb_context, :irb_workspace
|
15
|
+
|
16
|
+
def irb_execute(line, &eval)
|
17
|
+
@irb_context_manager.define_eval &eval if block_given?
|
18
|
+
execute line
|
19
|
+
end
|
20
|
+
|
21
|
+
def expect_inspect(expect=nil, comment=nil)
|
22
|
+
if expect.nil? && @irb_context.io.is_a?(IRB::ReadlineInputMethod)
|
23
|
+
puts "What inspect value do you expect (only the static content, press up to get the last one):"
|
24
|
+
history = @irb_context.io.history
|
25
|
+
@irb_context.io.history = [inspect_last_value]
|
26
|
+
@irb_context.io.prompt = "fuzzy expectation: "
|
27
|
+
input = @irb_context.io.gets.strip
|
28
|
+
@irb_context.io.history = history
|
29
|
+
expect = input.empty? ? expect : input
|
30
|
+
end
|
31
|
+
super expect, comment
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.start_irb
|
35
|
+
require_relative "irb_patch"
|
36
|
+
started = Extension.apply :start_irb, nil, true
|
37
|
+
IRB.start unless started
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "irb"
|
2
|
+
require_relative "irb_exspec"
|
3
|
+
|
4
|
+
require 'io/console'
|
5
|
+
|
6
|
+
module IRB
|
7
|
+
class Context
|
8
|
+
alias_method :_initialize, :initialize
|
9
|
+
alias_method :_evaluate, :evaluate
|
10
|
+
|
11
|
+
def initialize(irb, workspace = nil, input_method = nil, output_method = nil)
|
12
|
+
_initialize(irb, workspace, input_method, output_method)
|
13
|
+
@exspec = Exspec::IrbExspec.new self, @workspace
|
14
|
+
end
|
15
|
+
|
16
|
+
def evaluate(line, line_no)
|
17
|
+
@exspec.irb_execute line do |instruction|
|
18
|
+
_evaluate instruction, line_no
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def inspect
|
23
|
+
"#<#{self.class.name}>"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class ReadlineInputMethod
|
28
|
+
def history
|
29
|
+
HISTORY.collect.to_a
|
30
|
+
end
|
31
|
+
|
32
|
+
def history=(value)
|
33
|
+
HISTORY.clear
|
34
|
+
HISTORY.push(*value)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class RubyLex
|
40
|
+
alias_method :_lex, :lex
|
41
|
+
|
42
|
+
def lex
|
43
|
+
if peek_equal?(Exspec::COMMAND_PREFIX)
|
44
|
+
identify_comment
|
45
|
+
ungetc
|
46
|
+
token
|
47
|
+
return get_readed
|
48
|
+
end
|
49
|
+
_lex
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Exspec
|
2
|
+
class Logger
|
3
|
+
def initialize
|
4
|
+
@log = []
|
5
|
+
@enabled = true
|
6
|
+
@erased_last = false
|
7
|
+
end
|
8
|
+
|
9
|
+
def enabled?
|
10
|
+
@enabled
|
11
|
+
end
|
12
|
+
|
13
|
+
def enabled=(value)
|
14
|
+
@enabled = value
|
15
|
+
end
|
16
|
+
|
17
|
+
def entries
|
18
|
+
@log
|
19
|
+
end
|
20
|
+
|
21
|
+
def instructions
|
22
|
+
@log.map{ |entry| entry[:instruction] }
|
23
|
+
end
|
24
|
+
|
25
|
+
def clear
|
26
|
+
@log.clear
|
27
|
+
end
|
28
|
+
|
29
|
+
def log(instruction, value)
|
30
|
+
if @enabled
|
31
|
+
instruction.gsub! /\n+/, ";\n"
|
32
|
+
@log << {:instruction => instruction.strip, :value => value}
|
33
|
+
@erased_last = false
|
34
|
+
end
|
35
|
+
value
|
36
|
+
end
|
37
|
+
|
38
|
+
def erase_last_instruction
|
39
|
+
erasing = !@erased_last
|
40
|
+
if erasing
|
41
|
+
@log.pop
|
42
|
+
@erased_last = true
|
43
|
+
end
|
44
|
+
erasing
|
45
|
+
end
|
46
|
+
|
47
|
+
def last_instruction
|
48
|
+
return nil if @log.empty?
|
49
|
+
@log.last[:instruction]
|
50
|
+
end
|
51
|
+
|
52
|
+
def last_value
|
53
|
+
return nil if @log.empty?
|
54
|
+
@log.last[:value]
|
55
|
+
end
|
56
|
+
|
57
|
+
def last_entry
|
58
|
+
return nil if @log.empty?
|
59
|
+
@log.last
|
60
|
+
end
|
61
|
+
|
62
|
+
def without_logging
|
63
|
+
enabled = enabled?
|
64
|
+
self.enabled = false
|
65
|
+
val = yield
|
66
|
+
ensure
|
67
|
+
self.enabled = enabled
|
68
|
+
val
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Exspec
|
2
|
+
class RegressionTestReporter < Reporter
|
3
|
+
SpecFailedError = Class.new(StandardError)
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@indent = 0
|
7
|
+
@specs = []
|
8
|
+
@failed = []
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
alias_method :_puts, :puts
|
13
|
+
alias_method :_print, :print
|
14
|
+
|
15
|
+
attr_reader :specs, :failed
|
16
|
+
|
17
|
+
def indent
|
18
|
+
" " * (@indent * 4)
|
19
|
+
end
|
20
|
+
|
21
|
+
def puts(text); end
|
22
|
+
def print(text); end
|
23
|
+
|
24
|
+
def puts_indented(text)
|
25
|
+
_puts "" if !@on_new_line
|
26
|
+
_puts indent + text
|
27
|
+
@on_new_line = true
|
28
|
+
$stdout.flush
|
29
|
+
end
|
30
|
+
|
31
|
+
def print_indented(text)
|
32
|
+
_print indent if @on_new_line
|
33
|
+
_print text
|
34
|
+
@on_new_line = false
|
35
|
+
$stdout.flush
|
36
|
+
end
|
37
|
+
|
38
|
+
def start_stack(spec)
|
39
|
+
puts_indented "Start spec stack #{spec.full_name}"
|
40
|
+
@indent += 1
|
41
|
+
end
|
42
|
+
|
43
|
+
def finish_stack(spec)
|
44
|
+
@indent -= 1
|
45
|
+
puts_indented "Finished spec stack #{spec.full_name}"
|
46
|
+
puts_indented "---------------------------------" if @indent == 0
|
47
|
+
end
|
48
|
+
|
49
|
+
def start_spec(spec)
|
50
|
+
@spec = spec
|
51
|
+
@specs << spec unless @specs.include?(spec)
|
52
|
+
puts_indented "Start test #{spec.full_name}"
|
53
|
+
@indent += 1
|
54
|
+
end
|
55
|
+
|
56
|
+
def finish_spec(spec)
|
57
|
+
@indent -= 1
|
58
|
+
puts_indented "Finished test #{spec.full_name}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def execute_instruction(instruction, index, line)
|
62
|
+
@line = line
|
63
|
+
@instruction = instruction
|
64
|
+
print_indented "."
|
65
|
+
end
|
66
|
+
|
67
|
+
def skip_signal
|
68
|
+
raise SkipSignal
|
69
|
+
end
|
70
|
+
|
71
|
+
def spec_failed(message)
|
72
|
+
failed << @spec unless failed.include?(@spec)
|
73
|
+
puts_indented "==> #{message}"
|
74
|
+
_print "XXXX"
|
75
|
+
puts_indented "(line: #{@line}, instruction: \"#{@instruction}\")"
|
76
|
+
raise SpecFailedError.new message
|
77
|
+
end
|
78
|
+
|
79
|
+
def spec_succeeded(message)
|
80
|
+
puts_indented "==> #{message}"
|
81
|
+
end
|
82
|
+
|
83
|
+
def exception(exception)
|
84
|
+
if exception.is_a?(SpecFailedError)
|
85
|
+
raise exception
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def show_comment(text)
|
90
|
+
puts_indented("#" + text)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Exspec
|
2
|
+
class Reporter
|
3
|
+
def start_stack(spec); end
|
4
|
+
def finish_stack(spec); end
|
5
|
+
def start_spec(spec); end
|
6
|
+
def finish_spec(spec); end
|
7
|
+
def execute_instruction(instruction, index, line); end
|
8
|
+
def executed_instruction(instruction, index, line); end
|
9
|
+
def exception(exception); end
|
10
|
+
def skip_signal(breaking); end
|
11
|
+
def show_comment(text); end
|
12
|
+
|
13
|
+
def puts(text)
|
14
|
+
Kernel.puts text
|
15
|
+
end
|
16
|
+
|
17
|
+
def print(text)
|
18
|
+
Kernel.print text
|
19
|
+
end
|
20
|
+
|
21
|
+
def spec_failed(message)
|
22
|
+
puts message
|
23
|
+
end
|
24
|
+
|
25
|
+
def spec_succeeded(message)
|
26
|
+
puts message
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/exspec/spec.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
require "active_support/core_ext"
|
2
|
+
|
3
|
+
class Spec
|
4
|
+
def self.name(description)
|
5
|
+
name = File.basename(description, File.extname(description))
|
6
|
+
name = name.split(".").last.strip
|
7
|
+
name.gsub(/ +/, "_").underscore
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(spec_manager, name, file, parent=nil)
|
11
|
+
@spec_manager = spec_manager
|
12
|
+
@name = name
|
13
|
+
@file = file
|
14
|
+
@parent = parent
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :name, :file
|
18
|
+
|
19
|
+
def full_name
|
20
|
+
".#{stack.map(&:name).join "."}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def description
|
24
|
+
name.gsub("_", " ").capitalize + "."
|
25
|
+
end
|
26
|
+
|
27
|
+
def full_description
|
28
|
+
stack.map(&:description).join " "
|
29
|
+
end
|
30
|
+
|
31
|
+
def parent
|
32
|
+
@parent || @spec_manager.parent(self)
|
33
|
+
end
|
34
|
+
|
35
|
+
def exist?
|
36
|
+
File.file? file
|
37
|
+
end
|
38
|
+
|
39
|
+
def for_instructions(&block)
|
40
|
+
content = File.read(file)
|
41
|
+
buffer = []
|
42
|
+
index = 0
|
43
|
+
content.each_line.with_index do |line, line_index|
|
44
|
+
line.strip!
|
45
|
+
if line.end_with? ";"
|
46
|
+
buffer << line.chop
|
47
|
+
else
|
48
|
+
buffer << line
|
49
|
+
block.call buffer.join("\n"), index, (line_index + 1)
|
50
|
+
buffer.clear
|
51
|
+
index += 1
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def stack
|
57
|
+
return @stack if @stack
|
58
|
+
reverse_stack = []
|
59
|
+
reverse_stack << self
|
60
|
+
spec = self
|
61
|
+
while spec = spec.parent
|
62
|
+
reverse_stack << spec
|
63
|
+
end
|
64
|
+
@stack = reverse_stack.reverse
|
65
|
+
end
|
66
|
+
|
67
|
+
def directory
|
68
|
+
file.chomp(File.extname(file))
|
69
|
+
end
|
70
|
+
|
71
|
+
def load
|
72
|
+
@spec_manager.exspec.load self
|
73
|
+
end
|
74
|
+
|
75
|
+
def run
|
76
|
+
@spec_manager.exspec.run self
|
77
|
+
end
|
78
|
+
|
79
|
+
def run
|
80
|
+
@spec_manager.exspec.run_stack self
|
81
|
+
end
|
82
|
+
|
83
|
+
def include
|
84
|
+
@spec_manager.exspec.include self
|
85
|
+
end
|
86
|
+
|
87
|
+
def children
|
88
|
+
@spec_manager.specs self
|
89
|
+
end
|
90
|
+
|
91
|
+
def hash
|
92
|
+
file.hash
|
93
|
+
end
|
94
|
+
|
95
|
+
def ==(spec)
|
96
|
+
return false if !spec.is_a?(self.class)
|
97
|
+
file.eql? spec.file
|
98
|
+
end
|
99
|
+
|
100
|
+
def eql?(spec)
|
101
|
+
self == spec
|
102
|
+
end
|
103
|
+
|
104
|
+
def inspect
|
105
|
+
"#<Spec:#{full_name}>"
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require_relative "spec"
|
3
|
+
|
4
|
+
module Exspec
|
5
|
+
class SpecManager
|
6
|
+
def initialize(exspec)
|
7
|
+
@exspec = exspec
|
8
|
+
@current_spec = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :exspec
|
12
|
+
attr_accessor :current_spec
|
13
|
+
|
14
|
+
def save(logger, description)
|
15
|
+
spec = create_spec description
|
16
|
+
file = spec.file
|
17
|
+
FileUtils.mkdir_p(File.dirname(file))
|
18
|
+
File.open(file, "w") do |f|
|
19
|
+
f.write(logger.instructions.join "\n")
|
20
|
+
end
|
21
|
+
@current_spec = spec
|
22
|
+
end
|
23
|
+
|
24
|
+
def spec(description)
|
25
|
+
return description if description.kind_of?(Spec) || description.nil?
|
26
|
+
|
27
|
+
description = description.strip
|
28
|
+
parent = description.start_with?(SPEC_SEPARATOR) ? nil : current_spec
|
29
|
+
parent_dir = parent.nil? ? TEST_DIR : parent.directory
|
30
|
+
file = File.expand_path(description, parent_dir)
|
31
|
+
return create_spec file if File.file?(file)
|
32
|
+
|
33
|
+
current = parent
|
34
|
+
description.split(SPEC_SEPARATOR).each do |spec|
|
35
|
+
spec.strip!
|
36
|
+
next if spec.empty?
|
37
|
+
current = create_spec spec, current
|
38
|
+
end
|
39
|
+
current
|
40
|
+
end
|
41
|
+
|
42
|
+
def parent(description)
|
43
|
+
spec = spec(description)
|
44
|
+
parent_dir = File.dirname(spec.file)
|
45
|
+
parent_file = parent_dir + SPEC_EXTENSION
|
46
|
+
return nil if parent_dir == TEST_DIR || !File.exist?(parent_file)
|
47
|
+
parent_name = Spec.name(parent_file)
|
48
|
+
create_spec parent_name, nil, parent_file
|
49
|
+
end
|
50
|
+
|
51
|
+
def specs(parent=current_spec, recursive=false)
|
52
|
+
parent = "" if parent.nil?
|
53
|
+
if parent.is_a?(String)
|
54
|
+
parent = parent.strip
|
55
|
+
relative_directory = current_spec.nil? ? TEST_DIR : current_spec.directory
|
56
|
+
parent_directory = File.expand_path(parent, relative_directory)
|
57
|
+
return find_specs(parent_directory, recursive) if File.directory?(parent_directory)
|
58
|
+
end
|
59
|
+
parent = spec(parent)
|
60
|
+
return [] if parent.nil? || !parent.exist?
|
61
|
+
find_specs(parent.directory, recursive)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def file(name, parent)
|
67
|
+
directory = parent.nil? ? TEST_DIR : parent.file.chomp(SPEC_EXTENSION)
|
68
|
+
File.join(directory, name + SPEC_EXTENSION)
|
69
|
+
end
|
70
|
+
|
71
|
+
def create_spec(description, parent=current_spec, file=nil)
|
72
|
+
name = Spec.name description
|
73
|
+
parent = (parent.nil? || !parent.exist?) ? nil : parent
|
74
|
+
file = File.file?(description) ? description : file(name, parent) if file.nil?
|
75
|
+
Spec.new self, name, file, parent
|
76
|
+
end
|
77
|
+
|
78
|
+
def find_specs(directory, recursively=false)
|
79
|
+
specs = []
|
80
|
+
return specs unless File.directory? directory
|
81
|
+
Dir.entries(directory).sort.each do |file|
|
82
|
+
next if file.gsub(".", "").empty?
|
83
|
+
file = File.expand_path(file, directory)
|
84
|
+
if File.directory? file
|
85
|
+
specs.push(*find_specs(file, recursively)) if recursively
|
86
|
+
elsif file.end_with? SPEC_EXTENSION
|
87
|
+
specs << create_spec(file)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
specs
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Exspec
|
2
|
+
class SpecRunner
|
3
|
+
def initialize(exspec)
|
4
|
+
@exspec = exspec
|
5
|
+
end
|
6
|
+
|
7
|
+
delegate :reporter, :to => :@exspec
|
8
|
+
|
9
|
+
attr_accessor :break_on_skip_signal
|
10
|
+
|
11
|
+
def run(specs)
|
12
|
+
return run_specs specs if specs.is_a? Enumerable
|
13
|
+
spec = @exspec.spec(specs)
|
14
|
+
val = nil
|
15
|
+
reporter.start_spec spec
|
16
|
+
spec.for_instructions do |instruction, index, line|
|
17
|
+
reporter.execute_instruction instruction, index, line
|
18
|
+
begin
|
19
|
+
val = @exspec.execute instruction
|
20
|
+
rescue SkipSignal
|
21
|
+
reporter.skip_signal break_on_skip_signal
|
22
|
+
break if break_on_skip_signal
|
23
|
+
rescue Exception => e
|
24
|
+
reporter.exception e
|
25
|
+
ensure
|
26
|
+
reporter.executed_instruction instruction, index, line
|
27
|
+
end
|
28
|
+
end
|
29
|
+
val
|
30
|
+
ensure
|
31
|
+
reporter.finish_spec spec
|
32
|
+
end
|
33
|
+
|
34
|
+
def run_stack(spec)
|
35
|
+
spec = @exspec.spec(spec)
|
36
|
+
reporter.start_stack spec
|
37
|
+
spec.stack.each do |spec|
|
38
|
+
begin
|
39
|
+
run spec
|
40
|
+
rescue
|
41
|
+
break
|
42
|
+
end
|
43
|
+
end
|
44
|
+
ensure
|
45
|
+
reporter.finish_stack spec
|
46
|
+
end
|
47
|
+
|
48
|
+
def run_specs(specs)
|
49
|
+
specs.each do |spec|
|
50
|
+
@exspec.reset
|
51
|
+
run_stack(spec)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|