Exspec 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|