tiramisu 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -5
- data/.travis.yml +3 -0
- data/README.md +305 -3
- data/README.md.html +348 -0
- data/Rakefile +10 -1
- data/bin/tiramisu +4 -0
- data/lib/tiramisu.rb +117 -2
- data/lib/tiramisu/assert.rb +142 -0
- data/lib/tiramisu/core_ext.rb +102 -0
- data/lib/tiramisu/mock.rb +48 -0
- data/lib/tiramisu/mock/expectation.rb +44 -0
- data/lib/tiramisu/mock/expectation/raise.rb +34 -0
- data/lib/tiramisu/mock/expectation/return.rb +34 -0
- data/lib/tiramisu/mock/expectation/throw.rb +26 -0
- data/lib/tiramisu/mock/expectation/with.rb +38 -0
- data/lib/tiramisu/pretty_print.rb +55 -0
- data/lib/tiramisu/run.rb +77 -0
- data/lib/tiramisu/unit.rb +195 -0
- data/lib/tiramisu/util.rb +91 -0
- data/lib/tiramisu/util/assert_raise.rb +54 -0
- data/lib/tiramisu/util/assert_throw.rb +40 -0
- data/lib/tiramisu/util/refute_raise.rb +48 -0
- data/lib/tiramisu/util/refute_throw.rb +34 -0
- data/test/assert_test.rb +98 -0
- data/test/context_inheritance_test.rb +65 -0
- data/test/hooks_test.rb +36 -0
- data/test/raise_test.rb +66 -0
- data/test/receive_and_raise_test.rb +91 -0
- data/test/receive_and_return_test.rb +74 -0
- data/test/receive_and_throw_test.rb +74 -0
- data/test/receive_test.rb +48 -0
- data/test/receive_with_test.rb +62 -0
- data/test/refute_raise_test.rb +90 -0
- data/test/refute_throw_test.rb +42 -0
- data/test/setup.rb +25 -0
- data/test/skip_test.rb +40 -0
- data/test/throw_test.rb +58 -0
- data/tiramisu.gemspec +5 -4
- metadata +57 -11
- data/lib/tiramisu/version.rb +0 -3
@@ -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
|
data/lib/tiramisu/run.rb
ADDED
@@ -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
|