tiramisu 0.0.0 → 0.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.
@@ -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