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
data/Rakefile
CHANGED
@@ -1,2 +1,11 @@
|
|
1
|
-
require
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'bundler/gem_tasks'
|
2
4
|
|
5
|
+
root = File.expand_path('..', __FILE__)
|
6
|
+
Rake::TestTask.new do |t|
|
7
|
+
t.ruby_opts << '-r "%s/test/setup" -I "%s/lib"' % [root, root]
|
8
|
+
t.pattern = 'test/**/*_test.rb'
|
9
|
+
t.verbose = true
|
10
|
+
end
|
11
|
+
task default: :test
|
data/bin/tiramisu
ADDED
data/lib/tiramisu.rb
CHANGED
@@ -1,5 +1,120 @@
|
|
1
|
-
require
|
1
|
+
require 'tty-progressbar'
|
2
|
+
require 'tty-screen'
|
3
|
+
require 'pastel'
|
2
4
|
|
3
5
|
module Tiramisu
|
4
|
-
|
6
|
+
extend self
|
7
|
+
|
8
|
+
DEFAULT_PATTERN = '{spec,test}/**/{*_spec.rb,*_test.rb}'.freeze
|
9
|
+
GLOBAL_SETUPS = []
|
10
|
+
|
11
|
+
GenericFailure = Struct.new(:reason, :caller)
|
12
|
+
AssertionFailure = Struct.new(:object, :arguments, :caller)
|
13
|
+
|
14
|
+
Skip = Struct.new(:reason, :caller)
|
15
|
+
|
16
|
+
INDENT = ' '.freeze
|
17
|
+
PASTEL = Pastel.new
|
18
|
+
%w[
|
19
|
+
red
|
20
|
+
bright_red
|
21
|
+
green
|
22
|
+
blue
|
23
|
+
cyan
|
24
|
+
magenta
|
25
|
+
bold
|
26
|
+
underline
|
27
|
+
].each {|m| define_method(m) {|*a| PASTEL.__send__(m, *a)}}
|
28
|
+
|
29
|
+
def units
|
30
|
+
@units ||= []
|
31
|
+
end
|
32
|
+
|
33
|
+
def assertions
|
34
|
+
@assertions ||= {}
|
35
|
+
end
|
36
|
+
|
37
|
+
def skips
|
38
|
+
@skips ||= []
|
39
|
+
end
|
40
|
+
|
41
|
+
def define_spec label, block
|
42
|
+
define_unit_class(:spec, label, block, [].freeze)
|
43
|
+
end
|
44
|
+
|
45
|
+
def define_and_register_a_spec label, block
|
46
|
+
units << define_spec(label, block)
|
47
|
+
end
|
48
|
+
|
49
|
+
def define_context label, block, parent
|
50
|
+
define_unit_class(:context, label, block, [*parent.__ancestors__, parent].freeze)
|
51
|
+
end
|
52
|
+
|
53
|
+
def define_and_register_a_context label, block, parent
|
54
|
+
units << define_context(label, block, parent)
|
55
|
+
end
|
56
|
+
|
57
|
+
# define a class that will hold contexts and tests
|
58
|
+
#
|
59
|
+
# @param [String, Symbol] type
|
60
|
+
# @param [String, Symbol] label
|
61
|
+
# @param [Proc] block
|
62
|
+
# @param [Array] ancestors
|
63
|
+
# @return [Unit]
|
64
|
+
#
|
65
|
+
def define_unit_class type, label, block, ancestors
|
66
|
+
identity = identity_string(type, label, block).freeze
|
67
|
+
Class.new ancestors.last || Unit do
|
68
|
+
define_singleton_method(:__ancestors__) {ancestors}
|
69
|
+
define_singleton_method(:__identity__) {identity}
|
70
|
+
Tiramisu::GLOBAL_SETUPS.each {|b| class_exec(&b)}
|
71
|
+
# execute given block only after global setups executed and all utility methods defined
|
72
|
+
result = catch(:__tiramisu_skip__) {class_exec(&block)}
|
73
|
+
Tiramisu.skips << result if result.is_a?(Skip)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# define a module that when included will execute the given block on base
|
78
|
+
#
|
79
|
+
# @param [Proc] block
|
80
|
+
# @return [Module]
|
81
|
+
#
|
82
|
+
def define_unit_module block
|
83
|
+
block || raise(ArgumentError, 'missing block')
|
84
|
+
Module.new do
|
85
|
+
# any spec/context that will include this module will "inherit" it's logic
|
86
|
+
#
|
87
|
+
# @example
|
88
|
+
# EnumeratorSpec = spec 'Enumerator tests' do
|
89
|
+
# # some tests here
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# spec Array do
|
93
|
+
# include EnumeratorSpec
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# spec Hash do
|
97
|
+
# include EnumeratorSpec
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
define_singleton_method(:included) {|b| b.class_exec(&block)}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# stop any code and report a failure
|
105
|
+
def fail reason, caller
|
106
|
+
throw(:__tiramisu_status__, GenericFailure.new(Array(reason), caller))
|
107
|
+
end
|
108
|
+
|
109
|
+
def void_hooks
|
110
|
+
{before: {}, around: {}, after: {}}
|
111
|
+
end
|
5
112
|
end
|
113
|
+
|
114
|
+
require 'tiramisu/core_ext'
|
115
|
+
require 'tiramisu/pretty_print'
|
116
|
+
require 'tiramisu/mock'
|
117
|
+
require 'tiramisu/util'
|
118
|
+
require 'tiramisu/unit'
|
119
|
+
require 'tiramisu/assert'
|
120
|
+
require 'tiramisu/run'
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module Tiramisu
|
2
|
+
class Assert
|
3
|
+
|
4
|
+
def initialize object, action = :assert, caller = nil
|
5
|
+
@object = object
|
6
|
+
@caller = caller
|
7
|
+
@assert = action == :assert
|
8
|
+
@refute = action == :refute
|
9
|
+
@assert || @refute || Kernel.raise(ArgumentError, 'action should be either :assert or :refute')
|
10
|
+
end
|
11
|
+
|
12
|
+
instance_methods.each do |m|
|
13
|
+
# overriding all instance methods so received messages to be passed to tested object.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# assert(x).frozen?
|
17
|
+
# # `assert` returns a object that receives `frozen?` message and pass it to x
|
18
|
+
#
|
19
|
+
define_method m do |*a, &b|
|
20
|
+
__assert__(m, a, b)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# forward any missing method to tested object.
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# assert(some_array).include? x
|
28
|
+
# # object returned by `assert` does not respond to `include?`
|
29
|
+
# # so `include?` is passed to `some_array`
|
30
|
+
#
|
31
|
+
def method_missing m, *a, &b
|
32
|
+
__assert__(m, a, b)
|
33
|
+
end
|
34
|
+
|
35
|
+
# ensure the given block raises as expected
|
36
|
+
#
|
37
|
+
# @note if block given it will have precedence over arguments
|
38
|
+
#
|
39
|
+
# @example assertion pass if block raises whatever
|
40
|
+
# assert {some code}.raise
|
41
|
+
#
|
42
|
+
# @example assertion pass if block raises NameError
|
43
|
+
# assert {some code}.raise NameError
|
44
|
+
#
|
45
|
+
# @example assertion pass if block raises NameError and error message matches /blah/
|
46
|
+
# assert {some code}.raise NameError, /blah/
|
47
|
+
#
|
48
|
+
# @example assertion pass if block raises whatever error that matches /blah/
|
49
|
+
# assert {some code}.raise nil, /blah/
|
50
|
+
#
|
51
|
+
# @example assertion pass if validation block returns a positive value
|
52
|
+
# assert {some code}.raise {|e| e.is_a?(NameError) && e.message =~ /blah/}
|
53
|
+
#
|
54
|
+
#
|
55
|
+
# @example assertion pass if nothing raised
|
56
|
+
# refute {some code}.raise
|
57
|
+
# # same
|
58
|
+
# fail_if {some code}.raise
|
59
|
+
#
|
60
|
+
# @example assertion fails only if block raises a NameError.
|
61
|
+
# it may raise whatever but NameError. if nothing raised assertion will fail.
|
62
|
+
#
|
63
|
+
# fail_if {some code}.raise NameError
|
64
|
+
#
|
65
|
+
# @example assertion pass if raised error does not match /blah/
|
66
|
+
# if nothing raised assertion will fail.
|
67
|
+
#
|
68
|
+
# fail_if {some code}.raise nil, /blah/
|
69
|
+
#
|
70
|
+
# @example assertion will pass if raised error is not a NameError
|
71
|
+
# and error message does not match /blah/
|
72
|
+
# if nothing raised assertion will fail as well.
|
73
|
+
#
|
74
|
+
# fail_if {some code}.raise NameError, /blah/
|
75
|
+
#
|
76
|
+
def raise type = nil, message = nil, &block
|
77
|
+
failure = Tiramisu.__send__(
|
78
|
+
@assert ? :assert_raised_as_expected : :refute_raised_as_expected,
|
79
|
+
@object, type, message, block
|
80
|
+
)
|
81
|
+
Tiramisu.fail(failure, caller[0]) if failure
|
82
|
+
end
|
83
|
+
alias to_raise raise
|
84
|
+
|
85
|
+
# ensure given block thrown as expected
|
86
|
+
#
|
87
|
+
# @note if block given it will have precedence over arguments
|
88
|
+
#
|
89
|
+
# @example assertion pass if any symbol thrown
|
90
|
+
# assert {some code}.throw
|
91
|
+
#
|
92
|
+
# @example assertion pass only if :x symbol thrown
|
93
|
+
# assert {some code}.throw(:x)
|
94
|
+
#
|
95
|
+
# @example assertion pass only if given block validates thrown symbol
|
96
|
+
# assert {some code}.throw {|sym| sym == :x}
|
97
|
+
#
|
98
|
+
def throw expected_symbol = nil, &block
|
99
|
+
failure = Tiramisu.__send__(
|
100
|
+
@assert ? :assert_thrown_as_expected : :refute_thrown_as_expected,
|
101
|
+
@object, expected_symbol ? expected_symbol.to_sym : nil, block
|
102
|
+
)
|
103
|
+
Tiramisu.fail(failure, caller[0]) if failure
|
104
|
+
end
|
105
|
+
alias to_throw throw
|
106
|
+
|
107
|
+
# ensure given mock will receive expected message by the end of test
|
108
|
+
#
|
109
|
+
# @example
|
110
|
+
# test :auth do
|
111
|
+
# user = mock(User.new)
|
112
|
+
# expect(user).to_receive(:password)
|
113
|
+
# user.authenticate
|
114
|
+
# # by the end of test user should receive :password message,
|
115
|
+
# # otherwise the test will fail
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
# @param Symbol expected message
|
119
|
+
# @return [Mock::Expectation]
|
120
|
+
#
|
121
|
+
def receive expected_message
|
122
|
+
@object[:returned].__tiramisu__expectations__ rescue Kernel.raise(ArgumentError, '`receive` works only with predefined mocks')
|
123
|
+
@object[:returned].__tiramisu__expectations__.push(Mock::Expectation.new(expected_message.to_sym, @assert, caller[0])).last
|
124
|
+
end
|
125
|
+
alias to_receive receive
|
126
|
+
|
127
|
+
private
|
128
|
+
def __assert__ message, arguments, block
|
129
|
+
Kernel.throw(:__tiramisu_status__, @object[:raised]) if @object[:raised]
|
130
|
+
result = __send_message__(@object[:returned], message, arguments, block)
|
131
|
+
return true if (@assert && result) || (@refute && !result)
|
132
|
+
Kernel.throw(:__tiramisu_status__, AssertionFailure.new(@object[:returned], arguments, @caller))
|
133
|
+
end
|
134
|
+
|
135
|
+
def __send_message__ object, message, arguments, block
|
136
|
+
if assertion = Tiramisu.assertions[message.to_sym]
|
137
|
+
return assertion.call(object, *arguments, &block)
|
138
|
+
end
|
139
|
+
object.__send__(message, *arguments, &block)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Kernel
|
2
|
+
|
3
|
+
# when called with a no-nil no-false argument it defines, register and returns a spec.
|
4
|
+
# when called with a nil or false argument it defines and returns a spec but does not register it.
|
5
|
+
# when called without arguments it defines a global setup that will run on each new created spec/context.
|
6
|
+
#
|
7
|
+
# @note a Unit Module is a regular Ruby Module that when included will execute the Unit's block on base
|
8
|
+
#
|
9
|
+
# @example define regular specs
|
10
|
+
# spec :some_spec do
|
11
|
+
# # ...
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# @example define a spec that will run on its own and can also be included into another specs/contexts
|
15
|
+
# shared = spec :some_shared_spec do
|
16
|
+
# # ...
|
17
|
+
# end
|
18
|
+
# # `shared` is now a spec good for inclusion in another specs/contexts.
|
19
|
+
#
|
20
|
+
# @example define a spec that wont run on itself but can be included into another specs/contexts
|
21
|
+
# Shared = spec nil do
|
22
|
+
# # ...
|
23
|
+
# end
|
24
|
+
# # `Shared` is now a module good for inclusion in another specs/contexts
|
25
|
+
# # but because `nil` used as first argument it wont run as a spec itself
|
26
|
+
#
|
27
|
+
# @example define a global setup, i.e. a block that will run inside any new defined spec/context
|
28
|
+
# spec do
|
29
|
+
# include Rack::Test # now Rack::Test will be included in any spec/context
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
def spec label = (noargs=true; nil), &block
|
33
|
+
block || raise(ArgumentError, 'missing block')
|
34
|
+
|
35
|
+
if noargs
|
36
|
+
# no arguments given, defining a global setup and returning
|
37
|
+
return Tiramisu::GLOBAL_SETUPS << block
|
38
|
+
end
|
39
|
+
|
40
|
+
if label
|
41
|
+
# a no-nil no-false argument given, defining a regular spec
|
42
|
+
Tiramisu.define_and_register_a_spec(label, block)
|
43
|
+
end
|
44
|
+
|
45
|
+
# defining a shared spec that wont run itself
|
46
|
+
# but can be included in another specs/contexts
|
47
|
+
Tiramisu.define_unit_module(block)
|
48
|
+
end
|
49
|
+
|
50
|
+
# when used out of tests it defines a assertion helper.
|
51
|
+
# the block should return a no-false no-nil value for assertion to pass.
|
52
|
+
# the block will receive tested object as first argument
|
53
|
+
# and any arguments passed to assertion as consequent ones.
|
54
|
+
# it will also receive the passed block.
|
55
|
+
#
|
56
|
+
# @example checks whether two arrays has same keys, orderlessly
|
57
|
+
# assert :has_same_keys_as do |a, b|
|
58
|
+
# a.keys.sort == b.keys.sort
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# spec :some_spec do
|
62
|
+
# test :some_test do
|
63
|
+
# a = [1, 2]
|
64
|
+
# b = [2, 1]
|
65
|
+
# assert(a).has_same_keys_as(b) # => true
|
66
|
+
# end
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# @example same assertion by multiple names
|
70
|
+
# assert :includes, :to_include do |a, b|
|
71
|
+
# a.keys.sort == b.keys.sort
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# spec :some_spec do
|
75
|
+
# test :some_test do
|
76
|
+
# a = [1, 2]
|
77
|
+
# assert(a).includes(1) # => true
|
78
|
+
# # same
|
79
|
+
# expect(a).to_include(1) # => true
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# @param [Array] *labels
|
84
|
+
#
|
85
|
+
def assert *labels, &block
|
86
|
+
labels.any? || raise(ArgumentError, 'Wrong number of arguments, 0 for 1+')
|
87
|
+
block || raise(ArgumentError, 'missing block')
|
88
|
+
labels.each {|label| Tiramisu.assertions[label.to_sym] = block}
|
89
|
+
end
|
90
|
+
|
91
|
+
# stop executing any code and report a failure
|
92
|
+
#
|
93
|
+
# @example
|
94
|
+
# x > y || fail('x should be greater than y')
|
95
|
+
#
|
96
|
+
# @param reason
|
97
|
+
#
|
98
|
+
def fail *reason
|
99
|
+
reason.empty? && raise(ArgumentError, 'Wrong number or arguments, 0 for 1+')
|
100
|
+
Tiramisu.fail(reason.flatten, caller[0])
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Tiramisu
|
2
|
+
class Mock
|
3
|
+
|
4
|
+
def initialize object
|
5
|
+
@object = object
|
6
|
+
end
|
7
|
+
|
8
|
+
instance_methods.each do |m|
|
9
|
+
define_method m do |*a, &b|
|
10
|
+
__tiramisu__register_and_send__(m, a, b)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def method_missing m, *a, &b
|
15
|
+
__tiramisu__register_and_send__(m, a, b)
|
16
|
+
end
|
17
|
+
|
18
|
+
def __tiramisu__expectations__
|
19
|
+
@__tiramisu__expectations__ ||= []
|
20
|
+
end
|
21
|
+
|
22
|
+
def __tiramisu__messages__
|
23
|
+
@__tiramisu__messages__ ||= {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def __tiramisu__validate_messages__
|
27
|
+
__tiramisu__messages__.freeze
|
28
|
+
__tiramisu__expectations__.each do |expectation|
|
29
|
+
expectation.validate(__tiramisu__messages__)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def __tiramisu__register_and_send__ m, a, b
|
35
|
+
log = {arguments: a, block: b, caller: Tiramisu.relative_location(caller[1])}
|
36
|
+
(__tiramisu__messages__[m] ||= []).push(log)
|
37
|
+
begin
|
38
|
+
log[:returned] = @object.__send__(m, *a, &b)
|
39
|
+
rescue UncaughtThrowError => e
|
40
|
+
log[:thrown] = Tiramisu.extract_thrown_symbol(e)
|
41
|
+
rescue Exception => e
|
42
|
+
log[:raised] = e
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
require 'tiramisu/mock/expectation'
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Tiramisu
|
2
|
+
class Mock
|
3
|
+
class Expectation
|
4
|
+
|
5
|
+
attr_reader :expected_message, :received_messages
|
6
|
+
|
7
|
+
def initialize expected_message, assert, caller
|
8
|
+
@expected_message = expected_message.to_sym
|
9
|
+
@assert = assert
|
10
|
+
@caller = caller
|
11
|
+
end
|
12
|
+
|
13
|
+
def validate received_messages
|
14
|
+
@received_messages = received_messages[expected_message] || []
|
15
|
+
return refute_message_received unless @assert
|
16
|
+
assert_message_received
|
17
|
+
assert_message_received_with_correct_arguments
|
18
|
+
assert_message_returned_correct_value
|
19
|
+
assert_message_raised_as_expected
|
20
|
+
assert_message_thrown_as_expected
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def assert_message_received
|
25
|
+
Tiramisu.fail('Expected %s to receive %s message' % [
|
26
|
+
Tiramisu.pp(@object),
|
27
|
+
Tiramisu.pp(expected_message)
|
28
|
+
], @caller) if received_messages.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
def refute_message_received
|
32
|
+
Tiramisu.fail('Not Expected %s to receive %s message' % [
|
33
|
+
Tiramisu.pp(@object),
|
34
|
+
Tiramisu.pp(expected_message)
|
35
|
+
], @caller) if received_messages.any?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
require 'tiramisu/mock/expectation/with'
|
42
|
+
require 'tiramisu/mock/expectation/return'
|
43
|
+
require 'tiramisu/mock/expectation/raise'
|
44
|
+
require 'tiramisu/mock/expectation/throw'
|