eetee 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,62 @@
1
+ module EEtee
2
+ class Reporter
3
+ attr_reader :failures, :errors, :assertion_count, :test_count
4
+
5
+ def initialize
6
+ @errors = []
7
+ @failures = []
8
+ @empty = []
9
+
10
+ @indent = 0
11
+ @assertion_count = 0
12
+ @test_count = 0
13
+
14
+ @started_at = Time.now
15
+ end
16
+
17
+ def increment_assertions
18
+ @assertion_count += 1
19
+ end
20
+
21
+ def around_context(ctx, &block)
22
+ @indent += 1
23
+ block.call()
24
+ ensure
25
+ @indent -= 1
26
+ end
27
+
28
+ def around_test(test, &block)
29
+ before_assertions = @assertion_count
30
+
31
+ block.call()
32
+
33
+ if before_assertions == @assertion_count
34
+ @empty << test
35
+ :empty
36
+ else
37
+ @test_count += 1
38
+ :ok
39
+ end
40
+
41
+ rescue EEtee::AssertionFailed => err
42
+ @failures << err
43
+ :failed
44
+
45
+ rescue EEtee::Error => err
46
+ @errors << err
47
+ :error
48
+ end
49
+
50
+ end
51
+
52
+
53
+ def report_results
54
+ # no op
55
+ end
56
+
57
+ private
58
+ def elapsed_time
59
+ Time.now.to_i - @started_at.to_i
60
+ end
61
+
62
+ end
@@ -0,0 +1,123 @@
1
+ # encoding: utf-8
2
+
3
+ require 'term/ansicolor'
4
+
5
+ module EEtee
6
+ module Reporters
7
+
8
+ ##
9
+ # Advanced text output designed to be used in a open
10
+ # console.
11
+ #
12
+ class Console < Reporter
13
+ Color = Term::ANSIColor
14
+
15
+ def around_context(ctx, &block)
16
+ puts "" if @indent == 0
17
+ # iputs "# #{ctx.description}:"
18
+
19
+ iputs "#{Color.underscore}#{ctx.description}#{Color.reset}"
20
+ super
21
+
22
+ puts "" if @indent == 0
23
+ end
24
+
25
+ def around_test(test, &block)
26
+ iprint " ~ #{test.label}"
27
+
28
+ ret = super
29
+
30
+ # goto beginning of line
31
+ print "\e[G\e[K"
32
+
33
+
34
+ case ret
35
+ when :empty then iputs " #{Color.red}✘#{Color.reset} #{test.label}"
36
+ when :ok then iputs " #{Color.green}✔#{Color.reset} #{test.label}"
37
+ when :failed then iputs " #{Color.red}✘#{Color.reset} #{test.label}"
38
+ when :error then iputs " #{Color.red}☁ #{test.label}#{Color.reset} [#{@errors[-1].class}]"
39
+ end
40
+ end
41
+
42
+ def report_results
43
+ report_failures() unless @failures.empty?
44
+ report_errors() unless @errors.empty?
45
+ report_empty() unless @empty.empty?
46
+
47
+ puts ""
48
+ puts "#{@test_count} Tests / #{@assertion_count} Assertions"
49
+ puts "#{@errors.size} Errors / #{@failures.size} failures"
50
+ if elapsed_time() == 0
51
+ puts "Execution time: < 1s"
52
+ else
53
+ puts "Execution time: #{human_duration(elapsed_time)}"
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def spaces(str = " ")
60
+ str * @indent
61
+ end
62
+
63
+ def iprint(msg)
64
+ tmp = ' ' * @indent
65
+ Kernel.print("#{tmp}#{msg}")
66
+ end
67
+
68
+ def iputs(msg)
69
+ iprint("#{msg}\n")
70
+ end
71
+
72
+ def human_duration(secs)
73
+ [[60, 'seconds'], [60, 'minutes'], [24, 'hours'], [1000, 'days']].map do |count, name|
74
+ if secs > 0
75
+ secs, n = secs.divmod(count)
76
+ (n > 0) ? "#{n.to_i} #{name}" : nil
77
+ end
78
+ end.compact.reverse.join(' ')
79
+ end
80
+
81
+ def report_empty
82
+ puts "\nEmpty tests:"
83
+ @empty.each do |test|
84
+ puts "- #{test.label}"
85
+ end
86
+ end
87
+
88
+ def report_failures
89
+ puts "\nFailures:"
90
+ @failures.each do |f|
91
+ puts "- #{f.test.label}: #{f.message}"
92
+ dump_trace(f)
93
+ end
94
+ end
95
+
96
+ def report_errors
97
+ puts "\nErrors:"
98
+ @errors.each do |err|
99
+ puts "#{err.test.label}:"
100
+ puts " #{err.error.class} #{err.error}"
101
+ dump_trace(err.error)
102
+ end
103
+ end
104
+
105
+ def dump_trace(err, cleanup = true)
106
+ lines = err.backtrace
107
+
108
+ if cleanup
109
+ lines.reject! do |line|
110
+ line.include?('.rbenv')
111
+ end
112
+ end
113
+
114
+ lines.each do |line|
115
+ puts " #{line}"
116
+ end
117
+
118
+ end
119
+
120
+ end
121
+
122
+ end
123
+ end
@@ -0,0 +1,80 @@
1
+ module EEtee
2
+ module Reporters
3
+
4
+ ##
5
+ # Basic text output.
6
+ #
7
+ class Text < Reporter
8
+
9
+
10
+ def around_context(ctx, &block)
11
+ puts "" if @indent == 0
12
+ iputs "# #{ctx.description}:"
13
+ super
14
+ end
15
+
16
+ def around_test(test, &block)
17
+ iprint "- #{test.label}... "
18
+ case super
19
+ when :empty then puts "EMPTY"
20
+ when :ok then puts "OK"
21
+ when :failed then puts "FAILED"
22
+ when :error then puts "ERROR"
23
+ end
24
+ end
25
+
26
+ def report_results
27
+ report_failures() unless @failures.empty?
28
+ report_errors() unless @errors.empty?
29
+
30
+ puts ""
31
+ puts "#{@test_count} Tests / #{@assertion_count} Assertions"
32
+ puts "#{@errors.size} Errors / #{@failures.size} failures"
33
+ end
34
+
35
+ private
36
+ def iprint(msg)
37
+ tmp = ' ' * @indent
38
+ Kernel.print("#{tmp}#{msg}")
39
+ end
40
+
41
+ def iputs(msg)
42
+ iprint("#{msg}\n")
43
+ end
44
+
45
+ def report_failures
46
+ puts "\nFailures:"
47
+ @failures.each do |f|
48
+ puts "- #{f.test.label}: #{f.message}"
49
+ dump_trace(f)
50
+ end
51
+ end
52
+
53
+ def report_errors
54
+ puts "\nErrors:"
55
+ @errors.each do |err|
56
+ puts "#{err.test.label}:"
57
+ puts " #{err.error.class} #{err.error}"
58
+ dump_trace(err.error)
59
+ end
60
+ end
61
+
62
+ def dump_trace(err, cleanup = true)
63
+ lines = err.backtrace
64
+
65
+ if cleanup
66
+ lines.reject! do |line|
67
+ line.include?('gems')
68
+ end
69
+ end
70
+
71
+ lines.each do |line|
72
+ puts " #{line}"
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,62 @@
1
+ require 'forwardable'
2
+
3
+ module EEtee
4
+
5
+ class Runner
6
+ extend Forwardable
7
+ attr_reader :focus_mode
8
+
9
+ def_delegators :@reporter, :failures, :errors, :assertions, :test_count
10
+
11
+ def initialize
12
+ @reporter = EEtee.default_reporter_class.new
13
+ @focus_mode = false
14
+ end
15
+
16
+ def run(&block)
17
+ instance_eval(&block)
18
+ @reporter.report_results()
19
+ end
20
+
21
+ def run_pattern(pattern)
22
+ paths = []
23
+ caller_path = caller[0].split(':').first
24
+ pattern = File.expand_path("../#{pattern}", caller_path)
25
+
26
+ paths = Dir[pattern].to_a
27
+ run_files(paths)
28
+ end
29
+
30
+ ##
31
+ # run the files relative to the caller.
32
+ #
33
+ def run_files(paths)
34
+ paths.each do |path|
35
+ if File.exist?(path)
36
+ data = File.read(path)
37
+ if data =~ /^\s+should.*?,\s+:focus => true do$/
38
+ puts "Focus enabled."
39
+ @focus_mode = true
40
+ else
41
+ @focus_mode = false
42
+ end
43
+ instance_eval(data, path)
44
+ else
45
+ puts "!!! file does not exists: #{path}"
46
+ end
47
+ end
48
+
49
+ rescue SyntaxError => ex
50
+ puts ex.message
51
+ end
52
+
53
+ def report_results
54
+ @reporter.report_results()
55
+ end
56
+
57
+ def describe(description, &block)
58
+ Context.new(description, 0, @reporter, {}, @focus_mode, &block)
59
+ end
60
+ end
61
+
62
+ end
@@ -0,0 +1,27 @@
1
+ module EEtee
2
+ class Shared
3
+ attr_accessor :_reporter, :_level
4
+
5
+ include SharedContextMethods
6
+
7
+ @@shared = {}
8
+
9
+ def self.run(name, reporter, level, *args)
10
+ sh = @@shared[name]
11
+ sh._reporter = reporter
12
+ sh._level = level
13
+ sh.run(*args)
14
+ end
15
+
16
+
17
+ def initialize(name, &block)
18
+ @@shared[name] = self
19
+ @_block = block
20
+ end
21
+
22
+ def run(*args)
23
+ instance_exec(*args, &@_block)
24
+ end
25
+
26
+ end
27
+ end
data/lib/eetee/test.rb ADDED
@@ -0,0 +1,40 @@
1
+ require 'thread'
2
+
3
+ module EEtee
4
+
5
+ module TestBaseExtension
6
+ def run(&block)
7
+ block.call()
8
+ end
9
+ end
10
+
11
+ class Test
12
+ attr_reader :label, :reporter
13
+
14
+ include TestBaseExtension
15
+
16
+ def initialize(label, reporter, &block)
17
+ @label = label
18
+ @reporter = reporter || raise('missing reporter')
19
+ @reporter.around_test(self) do
20
+ run(&block)
21
+ end
22
+ end
23
+
24
+ def run(&block)
25
+ EEtee.current_test = self
26
+ super(&block)
27
+
28
+ rescue AssertionFailed => err
29
+ err.test = self
30
+ ::Kernel.raise
31
+
32
+ rescue => err
33
+ ::Kernel.raise Error.new(err, self)
34
+ ensure
35
+ EEtee.current_test = nil
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -0,0 +1,3 @@
1
+ module EEtee
2
+ VERSION = "0.0.1"
3
+ end
data/lib/eetee.rb ADDED
@@ -0,0 +1,65 @@
1
+ require_relative 'eetee/version'
2
+
3
+ require_relative 'eetee/reporter'
4
+ require_relative 'eetee/reporters/text'
5
+ require_relative 'eetee/reporters/console'
6
+
7
+ require_relative 'eetee/errors'
8
+ require_relative 'eetee/assertion_wrapper'
9
+ require_relative 'eetee/test'
10
+ require_relative 'eetee/context'
11
+ require_relative 'eetee/shared'
12
+ require_relative 'eetee/runner'
13
+
14
+
15
+
16
+ module EEtee
17
+ class << self
18
+ def default_reporter_class=(reporter)
19
+ @reporter = reporter
20
+ end
21
+
22
+ def default_reporter_class
23
+ @reporter
24
+ end
25
+
26
+ def enable_focus_mode=(value)
27
+ @enable_focus_mode = value
28
+ end
29
+
30
+ def enable_focus_mode
31
+ @enable_focus_mode
32
+ end
33
+ end
34
+
35
+
36
+ def self.current_test
37
+ Thread.current[:eetee_test]
38
+ end
39
+
40
+ def self.current_test=(test)
41
+ Thread.current[:eetee_test] = test
42
+ end
43
+
44
+
45
+ module AssertionHelpers
46
+ def should()
47
+ EEtee::AssertionWrapper.new(self)
48
+ end
49
+ end
50
+
51
+ def describe(description, enable_focus_mode = EEtee.enable_focus_mode, &block)
52
+ reporter = EEtee.default_reporter_class.new
53
+ Context.new(description, 0, reporter, {}, enable_focus_mode, &block)
54
+ reporter.report_results()
55
+ end
56
+
57
+ def shared(name, &block)
58
+ Shared.new(name, &block)
59
+ end
60
+
61
+ self.default_reporter_class = Reporters::Console
62
+ end
63
+
64
+ include EEtee
65
+ Object.__send__(:include, EEtee::AssertionHelpers)
@@ -0,0 +1,77 @@
1
+ require 'guard'
2
+ require 'guard/guard'
3
+
4
+ gem 'guard', '~> 1.5.3'
5
+
6
+ module Guard
7
+ class EEtee < Guard
8
+
9
+ # Initialize a Guard.
10
+ # @param [Array<Guard::Watcher>] watchers the Guard file watchers
11
+ # @param [Hash] options the custom Guard options
12
+ def initialize(watchers = [], options = {})
13
+ super
14
+ end
15
+
16
+ # Call once when Guard starts. Please override initialize method to init stuff.
17
+ # @raise [:task_has_failed] when start has failed
18
+ def start
19
+ puts "Guard::EEtee started."
20
+ end
21
+
22
+ # Called when `stop|quit|exit|s|q|e + enter` is pressed (when Guard quits).
23
+ # @raise [:task_has_failed] when stop has failed
24
+ def stop
25
+ puts "Guard::EEtee stopped."
26
+ end
27
+
28
+ # Called when `reload|r|z + enter` is pressed.
29
+ # This method should be mainly used for "reload" (really!) actions like reloading passenger/spork/bundler/...
30
+ # @raise [:task_has_failed] when reload has failed
31
+ def reload
32
+
33
+ end
34
+
35
+ # Called when just `enter` is pressed
36
+ # This method should be principally used for long action like running all specs/tests/...
37
+ # @raise [:task_has_failed] when run_all has failed
38
+ def run_all
39
+ end
40
+
41
+ # Called on file(s) modifications that the Guard watches.
42
+ # @param [Array<String>] paths the changes files or paths
43
+ # @raise [:task_has_failed] when run_on_change has failed
44
+ def run_on_changes(paths)
45
+ pid = Kernel.fork do
46
+ require 'eetee'
47
+ runner = EEtee::Runner.new
48
+ runner.run_files(paths)
49
+ runner.report_results()
50
+
51
+ tests = runner.test_count
52
+ failures = runner.failures.size + runner.errors.size
53
+
54
+ focus_mode = runner.focus_mode
55
+
56
+ if failures > 0
57
+ Notifier.notify("Specs: #{failures} Failures (#{tests} tests) #{focus_mode ? '(focus)' : ''}",
58
+ :image => :failed
59
+ )
60
+ else
61
+ Notifier.notify("Specs: OK (#{tests} tests) #{focus_mode ? '(focus)' : ''}",
62
+ :image => :success
63
+ )
64
+ end
65
+ end
66
+
67
+ Process.wait(pid)
68
+ end
69
+
70
+ # Called on file(s) deletions that the Guard watches.
71
+ # @param [Array<String>] paths the deleted files or paths
72
+ # @raise [:task_has_failed] when run_on_change has failed
73
+ def run_on_removals(paths)
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'eetee'
5
+
6
+ require 'eetee/ext/mocha'
7
+
8
+ Thread.abort_on_exception = true
9
+
10
+ # examples
11
+ require_relative '../examples/extensions/extension'
@@ -0,0 +1,134 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ module Helpers
4
+ def ensure_error_not_raised
5
+ ret = nil
6
+ begin
7
+ yield
8
+ rescue => err
9
+ ret = err
10
+ end
11
+
12
+ ret.should == nil
13
+ end
14
+
15
+ def ensure_error_raised(error_class)
16
+ ret = nil
17
+ begin
18
+ yield
19
+ rescue error_class => err
20
+ ret = err
21
+ end
22
+
23
+ ret.class.should == error_class
24
+ end
25
+ end
26
+
27
+ EEtee::Context.__send__(:include, Helpers)
28
+
29
+ describe 'AssertionWrapper' do
30
+ before do
31
+ @obj = stub('Object')
32
+ @wrapper = EEtee::AssertionWrapper.new(@obj)
33
+ end
34
+
35
+ should 'call method on the targeted object' do
36
+ @obj.expects(:to_zebra).returns(3)
37
+ @wrapper.to_zebra
38
+ end
39
+
40
+ should 'not raise an error if methods return anything else' do
41
+ @obj.expects(:to_zebra).returns(3)
42
+ ensure_error_not_raised do
43
+ @wrapper.to_zebra
44
+ end
45
+ end
46
+
47
+
48
+ describe 'assertions' do
49
+ describe 'any method' do
50
+ should 'not raise an error if it returns a non false value' do
51
+ @obj.expects(:to_zebra).returns(3)
52
+ @wrapper.to_zebra
53
+ end
54
+
55
+ should 'raise an error if it returns a false/nil value' do
56
+ @obj.expects(:to_zebra).returns(nil)
57
+ ensure_error_raised(EEtee::AssertionFailed) do
58
+ @wrapper.to_zebra
59
+ end
60
+ end
61
+
62
+ should '[inverted] not raise an error if it returns a false/nil value' do
63
+ @obj.expects(:to_zebra).returns(nil)
64
+ ensure_error_not_raised do
65
+ @wrapper.not.to_zebra
66
+ end
67
+ end
68
+ end
69
+
70
+ describe 'raise' do
71
+ should 'raise an error if block do not raises expected error' do
72
+ @obj.expects(:to_zebra).raises("damn !")
73
+ ensure_error_raised(EEtee::AssertionFailed) do
74
+ ->{ @wrapper.to_zebra }.should.raise(NoMethodError)
75
+ end
76
+ end
77
+
78
+ should 'not raise an error if block raises expected error' do
79
+ @obj.expects(:to_zebra).raises("damn !")
80
+ ensure_error_not_raised do
81
+ ->{ @wrapper.to_zebra }.should.raise(RuntimeError)
82
+ end
83
+ end
84
+
85
+ should '[inverted] raise an error if block raises expected error' do
86
+ @obj.expects(:to_zebra).raises("damn !")
87
+ ensure_error_raised(EEtee::AssertionFailed) do
88
+ ->{ @wrapper.to_zebra }.should.not.raise(RuntimeError)
89
+ end
90
+ end
91
+
92
+ end
93
+
94
+ describe 'be_a' do
95
+ should 'not raise an error if class match' do
96
+ w1 = EEtee::AssertionWrapper.new("string")
97
+ w1.be_a String
98
+
99
+ w1 = EEtee::AssertionWrapper.new(4)
100
+ w1.be_a Fixnum
101
+ end
102
+
103
+ should 'raise an error unless class match' do
104
+ w1 = EEtee::AssertionWrapper.new("string")
105
+ ->{ w1.be_a Fixnum }.should.raise(EEtee::AssertionFailed)
106
+
107
+ w1 = EEtee::AssertionWrapper.new(3)
108
+ ->{ w1.be_a String }.should.raise(EEtee::AssertionFailed)
109
+ end
110
+
111
+ end
112
+
113
+ describe 'be' do
114
+ should 'be transparent (syntax sugar)' do
115
+ w1 = EEtee::AssertionWrapper.new("string")
116
+ w1.be == "string"
117
+ end
118
+ end
119
+
120
+ describe 'close?' do
121
+ should 'not raise an error if number in range' do
122
+ w1 = EEtee::AssertionWrapper.new(3.4)
123
+ w1.close?(3, 0.4)
124
+ end
125
+
126
+ should 'raise an error if number outside range' do
127
+ w1 = EEtee::AssertionWrapper.new(3.4)
128
+ ->{ w1.close?(3, 0.3) }.should.raise(EEtee::AssertionFailed)
129
+ end
130
+ end
131
+
132
+ end
133
+
134
+ end