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.
- 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'
|