tiramisu 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,2 +1,11 @@
1
- require "bundler/gem_tasks"
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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path('../../lib', __FILE__))
3
+ require 'tiramisu'
4
+ Tiramisu.run(*$*)
data/lib/tiramisu.rb CHANGED
@@ -1,5 +1,120 @@
1
- require "tiramisu/version"
1
+ require 'tty-progressbar'
2
+ require 'tty-screen'
3
+ require 'pastel'
2
4
 
3
5
  module Tiramisu
4
- # Your code goes here...
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'