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