tiramisu 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,34 @@
1
+ module Tiramisu
2
+ class Mock
3
+ class Expectation
4
+
5
+ # ensure received message raises as expected
6
+ #
7
+ # @note if block given it will have precedence over arguments
8
+ #
9
+ # @example
10
+ # x = mock(X.new)
11
+ # expect(x).to_receive(:y).and_raise(NoMethodError)
12
+ # # call `x.y` for test to pass
13
+ #
14
+ def and_raise type = nil, message = nil, &block
15
+ @raise = block || [type, message]
16
+ end
17
+
18
+ def assert_message_raised_as_expected
19
+ return unless @raise
20
+ if @raise.is_a?(Proc)
21
+ received_messages.find {|log| @raise.call(log[:raised])} || Tiramisu.fail([
22
+ 'Looks like :%s message never raised as expected' % expected_message,
23
+ 'See validation block'
24
+ ], @caller)
25
+ else
26
+ received_messages.each do |log|
27
+ next unless f = Tiramisu.assert_raised_as_expected(log, *@raise)
28
+ Tiramisu.fail(f, log[:caller])
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ module Tiramisu
2
+ class Mock
3
+ class Expectation
4
+
5
+ # ensure received message returns expected value
6
+ #
7
+ # @note if block given it will have precedence over arguments
8
+ #
9
+ # @example
10
+ # n = mock(1)
11
+ # expect(n).to_receive(:+).with(1).and_return(2)
12
+ #
13
+ def and_return value = nil, &block
14
+ @return = block || value
15
+ end
16
+
17
+ private
18
+ def assert_message_returned_correct_value
19
+ return unless @return
20
+ if @return.is_a?(Proc)
21
+ received_messages.find {|log| @return.call(log[:returned])} || Tiramisu.fail([
22
+ 'Looks like :%s message never returned expected value' % expected_message,
23
+ 'See validation block'
24
+ ], @caller)
25
+ else
26
+ received_messages.find {|log| log[:returned] == @return} || Tiramisu.fail([
27
+ 'Looks like :%s message never returned expected value:' % expected_message,
28
+ Array(@return).map {|x| Tiramisu.pp(x)}.join(', ')
29
+ ], @caller)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,26 @@
1
+ module Tiramisu
2
+ class Mock
3
+ class Expectation
4
+
5
+ # ensure received message throws as expected
6
+ #
7
+ # @note if block given it will have precedence over arguments
8
+ #
9
+ # @example
10
+ # x = mock(X.new)
11
+ # expect(x).to_receive(:y).and_throw(:z)
12
+ #
13
+ def and_throw symbol = nil, &block
14
+ @throw = [symbol, block]
15
+ end
16
+
17
+ def assert_message_thrown_as_expected
18
+ return unless @throw
19
+ received_messages.each do |log|
20
+ next unless f = Tiramisu.assert_thrown_as_expected(log, *@throw)
21
+ Tiramisu.fail(f, log[:caller])
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,38 @@
1
+ module Tiramisu
2
+ class Mock
3
+ class Expectation
4
+
5
+ # ensure expected message received with correct arguments
6
+ #
7
+ # @note if block given it will have precedence over arguments
8
+ #
9
+ # @example
10
+ # test :some_test do
11
+ # some_object = mock(SomeObject.new)
12
+ # expect(some_object).to_receive(:some_method).with(:some, :args)
13
+ # # call `some_object.some_method(:some, :args)` for test to pass
14
+ # end
15
+ #
16
+ def with *args, &block
17
+ @with = block || args
18
+ self
19
+ end
20
+
21
+ private
22
+ def assert_message_received_with_correct_arguments
23
+ return unless @with
24
+ if @with.is_a?(Proc)
25
+ received_messages.find {|log| @with.call(log[:arguments])} || Tiramisu.fail([
26
+ 'Looks like :%s message never was called with expected arguments' % expected_message,
27
+ 'See validation block'
28
+ ], @caller)
29
+ else
30
+ received_messages.find {|log| log[:arguments] == @with} || Tiramisu.fail([
31
+ 'Looks like :%s message never was called with expected arguments:' % expected_message,
32
+ Array(@with).map {|x| Tiramisu.pp(x)}.join(', ')
33
+ ], @caller)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,55 @@
1
+ require 'pp'
2
+ require 'coderay'
3
+
4
+ # Stolen from [Pry](https://github.com/pry/pry)
5
+ #
6
+ # Copyright (c) 2013 John Mair (banisterfiend)
7
+ # Copyright (c) 2015 Slee Woo (sleewoo)
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining
10
+ # a copy of this software and associated documentation files (the
11
+ # 'Software'), to deal in the Software without restriction, including
12
+ # without limitation the rights to use, copy, modify, merge, publish,
13
+ # distribute, sublicense, and/or sell copies of the Software, and to
14
+ # permit persons to whom the Software is furnished to do so, subject to
15
+ # the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be
18
+ # included in all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
21
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
25
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
26
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+
28
+ module Tiramisu
29
+ def pp obj
30
+ out = ''
31
+ q = Tiramisu::PrettyPrint.new(out)
32
+ q.guard_inspect_key { q.pp(obj) }
33
+ q.flush
34
+ out
35
+ end
36
+
37
+ class PrettyPrint < ::PP
38
+ OBJECT_LITERAL_FORMAT = "\e[32m%s\e[0m".freeze
39
+
40
+ def text str, width = str.length
41
+ if str.include?("\e[")
42
+ super "%s\e[0m" % str, width
43
+ elsif str.start_with?('#<') || str == '=' || str == '>'
44
+ super highlight_object_literal(str), width
45
+ else
46
+ super CodeRay.scan(str, :ruby).term, width
47
+ end
48
+ end
49
+
50
+ private
51
+ def highlight_object_literal object_literal
52
+ OBJECT_LITERAL_FORMAT % object_literal
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,77 @@
1
+ module Tiramisu
2
+
3
+ def progress
4
+ @progress ||= TTY::ProgressBar.new ':current of :total [:bar]' do |cfg|
5
+ cfg.total = units.map {|u| u.tests.size}.reduce(:+) || 0
6
+ cfg.width = TTY::Screen.width
7
+ cfg.complete = '.'
8
+ end
9
+ end
10
+
11
+ def run pattern_or_files = DEFAULT_PATTERN
12
+ load_files(pattern_or_files)
13
+ units.shuffle.each do |unit|
14
+ # exceptions raised inside unit#__run__ will be treated as failures and pretty printed.
15
+ # any other exceptions will be treated as implementation errors and ugly printed.
16
+ unit.tests.keys.shuffle.each do |test|
17
+ status = unit.run(test)
18
+ if status.is_a?(Skip)
19
+ skips << status
20
+ else
21
+ status == :__tiramisu_passed__ || render_failure(unit, unit.tests[test], status)
22
+ end
23
+ progress.advance
24
+ end
25
+ end
26
+ render_skips
27
+ progress.log ''
28
+ end
29
+
30
+ def render_failure unit, test_uuid, failure
31
+ indent = ''
32
+ [*unit.__ancestors__, unit].each do |u|
33
+ progress.log indent + u.__identity__
34
+ indent << INDENT
35
+ end
36
+ progress.log indent + test_uuid
37
+ indent << INDENT
38
+ case failure
39
+ when Exception
40
+ render_exception(indent, failure)
41
+ when GenericFailure, AssertionFailure
42
+ render_caller(indent, failure.caller)
43
+ __send__('render_%s' % failure.class.name.split('::').last, indent, failure)
44
+ else
45
+ progress.log(indent + failure.inspect)
46
+ end
47
+ progress.log ''
48
+ end
49
+
50
+ def render_exception indent, failure
51
+ progress.log indent + underline.bright_red(failure.message)
52
+ pretty_backtrace(failure).each {|l| progress.log(indent + l)}
53
+ end
54
+
55
+ def render_GenericFailure indent, failure
56
+ Array(failure.reason).each {|l| progress.log(indent + l.to_s)}
57
+ end
58
+
59
+ def render_AssertionFailure indent, failure
60
+ progress.log indent + cyan('a: ') + pp(failure.object)
61
+ progress.log indent + cyan('b: ') + failure.arguments.map {|a| pp(a)}.join(', ')
62
+ end
63
+
64
+ def render_caller indent, caller
65
+ return unless caller
66
+ progress.log indent + underline.bright_red(readline(caller))
67
+ end
68
+
69
+ def render_skips
70
+ return if skips.empty?
71
+ progress.log ''
72
+ progress.log bold.magenta('Skips:')
73
+ skips.each do |skip|
74
+ progress.log ' %s (%s)' % [blue(skip.reason || 'skip'), relative_location(skip.caller)]
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,195 @@
1
+ module Tiramisu
2
+ class Unit
3
+
4
+ # @example
5
+ # assert(x) == y
6
+ # assert(x).include?(y)
7
+ # assert(x).nil?
8
+ # refute(a).include?(b)
9
+ # fail_if(a).include?(b)
10
+ [
11
+ :assert,
12
+ :refute
13
+ ].each do |meth|
14
+ define_method meth do |obj = nil, &block|
15
+ obj = block ? Tiramisu.call_block(block) : {returned: obj}.freeze
16
+ Tiramisu::Assert.new(obj, meth, caller[0])
17
+ end
18
+ end
19
+ alias expect assert
20
+ alias fail_if refute
21
+
22
+ # wrap given object into a proxy that will register and pass all messages
23
+ #
24
+ # @example
25
+ # user = mock(User.new)
26
+ # expect(user).to_receive(:some_method)
27
+ #
28
+ def mock obj
29
+ __mocks__.push(Mock.new(obj)).last
30
+ end
31
+
32
+ # stop executing current test and mark it as skipped
33
+ #
34
+ # @example
35
+ # test :something do
36
+ # skip "recheck this after fixing X"
37
+ # assert(x) == y # this wont run
38
+ # end
39
+ #
40
+ def skip reason = nil
41
+ throw(:__tiramisu_status__, Tiramisu::Skip.new(reason, caller[0]))
42
+ end
43
+
44
+ def __run__ test, before, around, after
45
+ __send__(before) if before
46
+ if around
47
+ __send__(around) {__send__(test)}
48
+ else
49
+ __send__(test)
50
+ end
51
+ __send__(after) if after
52
+ __mocks__.each(&:__tiramisu__validate_messages__)
53
+ :__tiramisu_passed__
54
+ rescue Exception => e
55
+ throw(:__tiramisu_status__, e)
56
+ end
57
+
58
+ private
59
+ def __mocks__
60
+ @__mocks__ ||= []
61
+ end
62
+ end
63
+
64
+ class << Unit
65
+
66
+ def spec
67
+ raise(NotImplementedError, 'Nested specs not supported. Please use a context instead')
68
+ end
69
+
70
+ # define a context inside current spec/context.
71
+ #
72
+ # @param label
73
+ # @param &block
74
+ #
75
+ def context label, &block
76
+ return unless block
77
+ Tiramisu.define_and_register_a_context(label, block, self)
78
+ end
79
+
80
+ # define a test
81
+ #
82
+ # @param label
83
+ # @param &block
84
+ #
85
+ def test label, &block
86
+ return unless block
87
+ tests[label] = Tiramisu.identity_string(:test, label, block)
88
+ define_method(tests[label], &block)
89
+ end
90
+ alias it test
91
+ alias should test
92
+
93
+ def tests
94
+ @__tiramisu_tests__ ||= {}
95
+ end
96
+
97
+ # run some code before/around/after any or specific tests
98
+ #
99
+ # @example call :define_users before any test (in current spec)
100
+ # spec User do
101
+ # before do
102
+ # @users = ...
103
+ # end
104
+ #
105
+ # test :login do
106
+ # # @users available here
107
+ # end
108
+ # end
109
+ #
110
+ # @example run only after :photos test
111
+ # spec User do
112
+ # after :photos do
113
+ # ...
114
+ # end
115
+ #
116
+ # test :photos do
117
+ # ...
118
+ # end
119
+ # end
120
+ #
121
+ # @example run around :authorize and :authenticate tests
122
+ # spec User do
123
+ # around :authorize, :authenticate do |&test|
124
+ # SomeApi.with_auth do
125
+ # test.call # run the test
126
+ # end
127
+ # end
128
+ #
129
+ # test :authorize do
130
+ # # will run inside `with_auth` block
131
+ # end
132
+ #
133
+ # test :authenticate do
134
+ # # same
135
+ # end
136
+ # end
137
+ #
138
+ # @note if multiple hooks defined for same test only the last one will run
139
+ # @note named hooks will have precedence over wildcard ones
140
+ #
141
+ # @example named hooks have precedence over wildcard ones
142
+ # before :math do
143
+ # # named hook, to run only before :math test
144
+ # end
145
+ #
146
+ # before do
147
+ # # wildcard hook, to run before any test
148
+ # end
149
+ #
150
+ # test :math do
151
+ # # only `bofore :math` hook will run here
152
+ # end
153
+ #
154
+ [
155
+ :before,
156
+ :around,
157
+ :after
158
+ ].each do |hook|
159
+ define_method hook do |*tests,&block|
160
+ block || raise(ArgumentError, 'block missing')
161
+ meth = :"__tiramisu_hooks_#{hook}_#{block.source_location.join}__"
162
+ tests = [:__tiramisu_hooks_any__] if tests.empty?
163
+ tests.each {|t| (hooks[hook] ||= {})[t] = meth}
164
+ define_method(meth, &block)
165
+ end
166
+ end
167
+
168
+ def hooks
169
+ @__tiramisu_hooks__ ||= Tiramisu.void_hooks
170
+ end
171
+
172
+ # skipping a whole spec/context
173
+ #
174
+ # @example
175
+ # spec Array do
176
+ # skip
177
+ #
178
+ # # code here wont be executed
179
+ # end
180
+ #
181
+ def skip reason = nil
182
+ throw(:__tiramisu_skip__, Tiramisu::Skip.new(reason, caller[0]))
183
+ end
184
+
185
+ def run test
186
+ tests[test] || raise(StandardError, 'Undefined test %s at "%s"' % [test.inspect, __identity__])
187
+ catch :__tiramisu_status__ do
188
+ allocate.__run__ tests[test],
189
+ hooks[:before][test] || hooks[:before][:__tiramisu_hooks_any__],
190
+ hooks[:around][test] || hooks[:around][:__tiramisu_hooks_any__],
191
+ hooks[:after][test] || hooks[:after][:__tiramisu_hooks_any__]
192
+ end
193
+ end
194
+ end
195
+ end