minispec 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/.pryrc +2 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +22 -0
  5. data/README.md +2140 -0
  6. data/Rakefile +11 -0
  7. data/bin/minispec +4 -0
  8. data/lib/minispec.rb +175 -0
  9. data/lib/minispec/api.rb +2 -0
  10. data/lib/minispec/api/class.rb +195 -0
  11. data/lib/minispec/api/class/after.rb +49 -0
  12. data/lib/minispec/api/class/around.rb +54 -0
  13. data/lib/minispec/api/class/before.rb +101 -0
  14. data/lib/minispec/api/class/helpers.rb +116 -0
  15. data/lib/minispec/api/class/let.rb +44 -0
  16. data/lib/minispec/api/class/tests.rb +33 -0
  17. data/lib/minispec/api/instance.rb +158 -0
  18. data/lib/minispec/api/instance/mocks/doubles.rb +36 -0
  19. data/lib/minispec/api/instance/mocks/mocks.rb +319 -0
  20. data/lib/minispec/api/instance/mocks/spies.rb +17 -0
  21. data/lib/minispec/api/instance/mocks/stubs.rb +105 -0
  22. data/lib/minispec/helpers.rb +1 -0
  23. data/lib/minispec/helpers/array.rb +56 -0
  24. data/lib/minispec/helpers/booleans.rb +108 -0
  25. data/lib/minispec/helpers/generic.rb +24 -0
  26. data/lib/minispec/helpers/mocks/expectations.rb +29 -0
  27. data/lib/minispec/helpers/mocks/spies.rb +36 -0
  28. data/lib/minispec/helpers/raise.rb +44 -0
  29. data/lib/minispec/helpers/throw.rb +29 -0
  30. data/lib/minispec/mocks.rb +11 -0
  31. data/lib/minispec/mocks/expectations.rb +77 -0
  32. data/lib/minispec/mocks/stubs.rb +178 -0
  33. data/lib/minispec/mocks/validations.rb +80 -0
  34. data/lib/minispec/mocks/validations/amount.rb +63 -0
  35. data/lib/minispec/mocks/validations/arguments.rb +161 -0
  36. data/lib/minispec/mocks/validations/caller.rb +43 -0
  37. data/lib/minispec/mocks/validations/order.rb +47 -0
  38. data/lib/minispec/mocks/validations/raise.rb +111 -0
  39. data/lib/minispec/mocks/validations/return.rb +74 -0
  40. data/lib/minispec/mocks/validations/throw.rb +91 -0
  41. data/lib/minispec/mocks/validations/yield.rb +141 -0
  42. data/lib/minispec/proxy.rb +201 -0
  43. data/lib/minispec/reporter.rb +185 -0
  44. data/lib/minispec/utils.rb +139 -0
  45. data/lib/minispec/utils/differ.rb +325 -0
  46. data/lib/minispec/utils/pretty_print.rb +51 -0
  47. data/lib/minispec/utils/raise.rb +123 -0
  48. data/lib/minispec/utils/throw.rb +140 -0
  49. data/minispec.gemspec +27 -0
  50. data/test/mocks/expectations/amount.rb +67 -0
  51. data/test/mocks/expectations/arguments.rb +126 -0
  52. data/test/mocks/expectations/caller.rb +55 -0
  53. data/test/mocks/expectations/generic.rb +35 -0
  54. data/test/mocks/expectations/order.rb +46 -0
  55. data/test/mocks/expectations/raise.rb +166 -0
  56. data/test/mocks/expectations/return.rb +71 -0
  57. data/test/mocks/expectations/throw.rb +113 -0
  58. data/test/mocks/expectations/yield.rb +109 -0
  59. data/test/mocks/spies/amount.rb +68 -0
  60. data/test/mocks/spies/arguments.rb +57 -0
  61. data/test/mocks/spies/generic.rb +61 -0
  62. data/test/mocks/spies/order.rb +38 -0
  63. data/test/mocks/spies/raise.rb +158 -0
  64. data/test/mocks/spies/return.rb +71 -0
  65. data/test/mocks/spies/throw.rb +113 -0
  66. data/test/mocks/spies/yield.rb +101 -0
  67. data/test/mocks/test__doubles.rb +98 -0
  68. data/test/mocks/test__expectations.rb +27 -0
  69. data/test/mocks/test__mocks.rb +197 -0
  70. data/test/mocks/test__proxies.rb +61 -0
  71. data/test/mocks/test__spies.rb +43 -0
  72. data/test/mocks/test__stubs.rb +427 -0
  73. data/test/proxified_asserts.rb +34 -0
  74. data/test/setup.rb +53 -0
  75. data/test/test__around.rb +58 -0
  76. data/test/test__assert.rb +510 -0
  77. data/test/test__before_and_after.rb +117 -0
  78. data/test/test__before_and_after_all.rb +71 -0
  79. data/test/test__helpers.rb +197 -0
  80. data/test/test__raise.rb +104 -0
  81. data/test/test__skip.rb +41 -0
  82. data/test/test__throw.rb +103 -0
  83. metadata +196 -0
@@ -0,0 +1,11 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'bundler/gem_tasks'
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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path('../../lib', __FILE__))
3
+ require 'minispec'
4
+ Minispec.run
@@ -0,0 +1,175 @@
1
+ require 'stringio'
2
+ require 'pp'
3
+ require 'coderay'
4
+ require 'diff/lcs'
5
+ require 'diff/lcs/hunk'
6
+
7
+ # private module.
8
+ # for internal use only.
9
+ module MiniSpec
10
+ extend self
11
+
12
+ # files loading pattern. relative to `Dir.pwd`
13
+ DEFAULT_PATTERN = "{spec,test}/**/{*_spec.rb,*_test.rb,test_*.rb}".freeze
14
+
15
+ SPEC_WRAPPERS = %w[
16
+ describe
17
+ context
18
+ section
19
+ ].freeze
20
+
21
+ TEST_WRAPPERS = %w[
22
+ test
23
+ testing
24
+ example
25
+ should
26
+ it
27
+ ].freeze
28
+
29
+ IMPORTABLES = %w[
30
+ tests
31
+ helpers
32
+ before
33
+ after
34
+ around
35
+ vars
36
+ continue_on_failures
37
+ ].map(&:to_sym).freeze
38
+
39
+ AFFIRMATIONS = %w[
40
+ is is?
41
+ are are?
42
+ was was?
43
+ does does?
44
+ did did?
45
+ have have?
46
+ has has?
47
+ assert
48
+ affirm
49
+ assume
50
+ assure
51
+ expect
52
+ verify
53
+ check
54
+ prove
55
+ would
56
+ will
57
+ ].freeze
58
+
59
+ NEGATIONS = %w[
60
+ refute
61
+ negate
62
+ fail_if
63
+ not_expected
64
+ assert_not
65
+ ].freeze
66
+
67
+
68
+ def source_location_cache file
69
+ return unless File.file?(file) && File.readable?(file)
70
+ (@source_location_cache ||= {})[file] ||= File.readlines(file)
71
+ end
72
+ end
73
+
74
+ MiniSpec::SPEC_WRAPPERS.each do |meth|
75
+ # top-level methods that allows to define specs using Minispec's DSL
76
+ #
77
+ # @example
78
+ #
79
+ # describe SomeSpec do
80
+ # # some tests
81
+ # end
82
+ #
83
+ # describe SomeAnotherSpec do
84
+ # # some another tests
85
+ # end
86
+ #
87
+ define_method meth do |subject, &proc|
88
+ spec_name = subject.to_s.freeze
89
+ spec = Class.new do
90
+ include Minispec
91
+ define_method(:subject) { subject }
92
+ # set spec name before executing the proc
93
+ # otherwise wrong spec name will be reported
94
+ define_singleton_method(:spec_name) { spec_name }
95
+ define_singleton_method(:spec_fullname) { spec_name }
96
+ define_singleton_method(:spec_proc) { proc }
97
+ define_singleton_method(:indent) { 0 }
98
+ end
99
+ spec.class_exec(&proc)
100
+ end
101
+ end
102
+
103
+ require 'minispec/utils'
104
+ require 'minispec/mocks'
105
+ require 'minispec/proxy'
106
+ require 'minispec/reporter'
107
+ require 'minispec/api'
108
+
109
+ # public module.
110
+ # to be used for inclusions by end users.
111
+ module Minispec
112
+ # extending Minispec module with MiniSpec::ClassAPI
113
+ # to be able to define global shared resources
114
+ #
115
+ # @example
116
+ # module Minispec
117
+ # around do
118
+ # # all specs includes Minispec module
119
+ # # so all tests will run inside this block
120
+ # end
121
+ #
122
+ # def some_utility_method
123
+ # # all specs will have this method
124
+ # end
125
+ #
126
+ # # etc
127
+ # end
128
+ #
129
+ extend MiniSpec::ClassAPI
130
+
131
+ class << self
132
+ attr_accessor :specs, :tests, :assertions
133
+
134
+ def included base
135
+ base.extend(MiniSpec::ClassAPI)
136
+ base.send(:include, MiniSpec::InstanceAPI)
137
+
138
+ # inserting global shared resources defined inside Minispec module
139
+ MiniSpec::IMPORTABLES.each do |importable|
140
+ base.send('import_%s' % importable, self)
141
+ end
142
+
143
+ specs.push(base) unless specs.include?(base)
144
+ end
145
+
146
+ def run opts = {}
147
+ files = opts[:files] || opts[:file]
148
+ files ||= File.basename($0) == 'minispec' && $*.any? && $*
149
+ files ||= Dir[File.join(Dir.pwd, opts[:pattern] || MiniSpec::DEFAULT_PATTERN)]
150
+ files = [files] unless files.is_a?(Array)
151
+
152
+ $:.include?(Dir.pwd) || $:.unshift(Dir.pwd)
153
+ lib = File.join(Dir.pwd, 'lib')
154
+ !$:.include?(lib) && File.directory?(lib) && $:.unshift(lib)
155
+
156
+ pwd = /\A#{Regexp.escape(Dir.pwd)}\//.freeze
157
+ files.each do |f|
158
+ path = File.expand_path(File.dirname(f), Dir.pwd).sub(pwd, '')
159
+ path = File.join(Dir.pwd, path.split('/').first)
160
+ $:.include?(path) || $:.unshift(path)
161
+ require(f)
162
+ end
163
+ reporter = opts[:reporter] || MiniSpec::Reporter.new
164
+ specs.each {|s| s.run(reporter)}
165
+ reporter.summary
166
+ exit(1) if reporter.failures?
167
+ end
168
+ end
169
+ end
170
+
171
+ Minispec.specs = []
172
+ Minispec.tests = 0
173
+ Minispec.assertions = 0
174
+
175
+ require 'minispec/helpers'
@@ -0,0 +1,2 @@
1
+ require 'minispec/api/class'
2
+ require 'minispec/api/instance'
@@ -0,0 +1,195 @@
1
+ module MiniSpec
2
+ module ClassAPI
3
+
4
+ # @example
5
+ # module CPUTests
6
+ # include Minispec
7
+ #
8
+ # # CPU related tests
9
+ # end
10
+ #
11
+ # module RAMTests
12
+ # include Minispec
13
+ #
14
+ # # RAM related tests
15
+ # end
16
+ #
17
+ # describe :MacBook do
18
+ # include CPUTests
19
+ # include RAMTests
20
+ #
21
+ # # will run CPU and RAM tests + any tests defined here
22
+ # end
23
+ #
24
+ def included base
25
+ base.send(:include, Minispec)
26
+ MiniSpec::IMPORTABLES.each do |importable|
27
+ base.send('import_%s' % importable, self)
28
+ end
29
+ end
30
+
31
+ # @example
32
+ # module CPUTests
33
+ # include Minispec
34
+ #
35
+ # # CPU related tests
36
+ # end
37
+ #
38
+ # module RAMTests
39
+ # include Minispec
40
+ #
41
+ # # RAM related tests
42
+ # end
43
+ #
44
+ # describe :MacBook do
45
+ # include CPUTests
46
+ # include RAMTests
47
+ #
48
+ # # we do not need :around hook nor included variables
49
+ # reset :around, :vars
50
+ #
51
+ # # will run CPU and RAM tests + any tests defined here
52
+ # end
53
+ #
54
+ def reset *importables
55
+ importables.each do |importable|
56
+ MiniSpec::IMPORTABLES.include?(inheritable.to_sym) || raise(ArgumentError,
57
+ 'Do not know how to reset %s. Use one of %s' % [inheritable.inspect, MiniSpec::IMPORTABLES*', '])
58
+ self.send('reset_%s' % inheritable)
59
+ end
60
+ end
61
+
62
+ # by default MiniSpec will stop evaluating a test on first failed assertion.
63
+ # `continue_on_failures true` will make MiniSpec continue evaluating regardless failures.
64
+ #
65
+ # @example set globally
66
+ #
67
+ # MiniSpec.setup do
68
+ # continue_on_failures true
69
+ # end
70
+ #
71
+ # @example set per spec
72
+ #
73
+ # describe SomeTest do
74
+ # continue_on_failures true
75
+ #
76
+ # # ...
77
+ # end
78
+ #
79
+ def continue_on_failures status
80
+ @continue_on_failures = status
81
+ end
82
+ def continue_on_failures?
83
+ @continue_on_failures
84
+ end
85
+
86
+ def import_continue_on_failures base
87
+ import_instance_variable(:continue_on_failures, base)
88
+ end
89
+ alias import_continue_on_failures_from import_continue_on_failures
90
+
91
+ def reset_continue_on_failures
92
+ remove_instance_variable(:@continue_on_failures)
93
+ end
94
+
95
+ def hooks_filter callbacks, filter
96
+ return callbacks unless filter
97
+ callbacks.map do |(matchers,proc)|
98
+ MiniSpec::Utils.any_match?(filter, matchers) ? [filter, matchers, proc] : nil
99
+ end.compact
100
+ end
101
+
102
+ def import_instance_variable var, base
103
+ return unless base.instance_variable_defined?('@%s' % var)
104
+ val = base.instance_variable_get('@%s' % var)
105
+ val.is_a?(Proc) ? send(var, &val) : send(var, val)
106
+ end
107
+
108
+ MiniSpec::SPEC_WRAPPERS.each do |meth|
109
+ # used to define nested specs
110
+ #
111
+ # inner specs will not share any of its stuff with parent spec
112
+ # nor will affect parent's state in any way,
113
+ # i.e. wont override any variables, setups nor tests etc.
114
+ # that's it, a inner spec is a isolated closure.
115
+ #
116
+ # @note if these methods used inside a class that included Minispec,
117
+ # the created spec will use parent spec as superclass.
118
+ #
119
+ # @example
120
+ # describe :Math do
121
+ #
122
+ # # Math tests
123
+ #
124
+ # describe :PI do
125
+ # # PI tests
126
+ # end
127
+ # end
128
+ #
129
+ define_method meth do |subject, opts = {}, &proc|
130
+ spec_name = subject.to_s.freeze
131
+ spec_fullname = [self.spec_fullname, spec_name].join(' / ').freeze
132
+ indent = self.indent + 2
133
+ args = self.is_a?(Class) && self.include?(Minispec) ? [self] : []
134
+ spec = Class.new *args do
135
+ include Minispec
136
+ define_method(:subject) { subject }
137
+ # set spec name before executing the proc
138
+ # otherwise wrong spec name will be reported in failures
139
+ define_singleton_method(:spec_name) { spec_name }
140
+ define_singleton_method(:spec_fullname) { spec_fullname }
141
+ define_singleton_method(:spec_proc) { proc }
142
+ define_singleton_method(:indent) { indent }
143
+ end
144
+ MiniSpec::IMPORTABLES.reject {|i| i == :tests}.each do |importable|
145
+ spec.send('import_%s' % importable, self)
146
+ end
147
+ spec.class_exec(&proc)
148
+ end
149
+ end
150
+
151
+ def spec_name; self.name end
152
+ alias :spec_fullname :spec_name
153
+ def spec_proc; nil end
154
+ def indent; 0 end
155
+
156
+ def run reporter
157
+ reporter.puts(spec_name, indent: indent)
158
+ instance = self.allocate
159
+ runner = proc do
160
+ instance.__ms__boot
161
+ tests.each_pair do |label,(verb,proc)|
162
+ reporter.print('%s %s ' % [verb, label], indent: indent + 2)
163
+
164
+ failures = instance.__ms__run_test(label)
165
+
166
+ if skipped = instance.__ms__skipped?
167
+ reporter.mark_as_skipped(spec_name, label, skipped)
168
+ next
169
+ end
170
+
171
+ if failures.empty?
172
+ reporter.mark_as_passed(spec_name, label)
173
+ next
174
+ end
175
+
176
+ reporter.mark_as_failed(spec_fullname, label, verb, proc, failures)
177
+ end
178
+ instance.__ms__halt
179
+ end
180
+ if around_all = around_all?
181
+ instance.instance_exec(runner, &around_all)
182
+ else
183
+ runner.call
184
+ end
185
+ reporter
186
+ rescue Exception => e
187
+ # catch exceptions raised inside :before_all/:after_all/:around_all hooks.
188
+ # exceptions raised inside tests are caught by instance#__ms__run_test
189
+ reporter.failed_specs << [spec_name, spec_proc, e]
190
+ reporter
191
+ end
192
+ end
193
+ end
194
+
195
+ Dir[File.expand_path('../class/**/*.rb', __FILE__)].each {|f| require(f)}
@@ -0,0 +1,49 @@
1
+ module MiniSpec
2
+ module ClassAPI
3
+
4
+ # same as `before` except it will run after matched tests.
5
+ # @note `after` hooks will run even on failed tests.
6
+ # however it wont run if some exception arise inside test.
7
+ def after *matchers, &proc
8
+ proc || raise(ArgumentError, 'block is missing')
9
+ matchers.flatten!
10
+ matchers = [:*] if matchers.empty?
11
+ return if after?.find {|x| x[0] == matchers && x[1].source_location == proc.source_location}
12
+ after?.push([matchers, proc])
13
+ end
14
+
15
+ def after? filter = nil
16
+ hooks_filter(@after ||= [], filter)
17
+ end
18
+
19
+ def reset_after
20
+ @after = []
21
+ end
22
+
23
+ # import `:after` and `:after_all` hooks from base
24
+ def import_after base
25
+ import_instance_variable(:after_all, base)
26
+ base.after?.each {|(m,p)| self.after(m, &p)}
27
+ end
28
+ alias import_after_from import_after
29
+
30
+ # code to run once after all tests finished.
31
+ # this callback will run only once.
32
+ # for callbacks that runs after any test @see #after
33
+ #
34
+ # @note this callback will run even if there are failed tests.
35
+ def after_all &proc
36
+ proc || raise(ArgumentError, 'block is missing')
37
+ @after_all = proc
38
+ end
39
+ alias after! after_all
40
+
41
+ def after_all?
42
+ @after_all
43
+ end
44
+
45
+ def reset_after_all
46
+ remove_instance_variable(:@after_all)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,54 @@
1
+ module MiniSpec
2
+ module ClassAPI
3
+
4
+ # a block to wrap each test evaluation
5
+ #
6
+ # @example
7
+ # describe SomeClass do
8
+ #
9
+ # around do |test|
10
+ # DB.connect
11
+ # test.run
12
+ # DB.disconnect
13
+ # end
14
+ # end
15
+ #
16
+ def around *matchers, &proc
17
+ proc || raise(ArgumentError, 'block is missing')
18
+ matchers.flatten!
19
+ matchers = [:*] if matchers.empty?
20
+ return if around?.find {|x| x[0] == matchers && x[1].source_location == proc.source_location}
21
+ around?.push([matchers, proc])
22
+ end
23
+
24
+ def around? filter = nil
25
+ hooks_filter(@around ||= [], filter)
26
+ end
27
+
28
+ def reset_around
29
+ @around = []
30
+ end
31
+
32
+ # import `:around` and `:around_all` from base
33
+ def import_around base
34
+ import_instance_variable(:around_all, base)
35
+ base.around?.each {|(m,p)| self.around(m, &p)}
36
+ end
37
+ alias import_around_from import_around
38
+
39
+ # a block to wrap all tests evaluation
40
+ def around_all &proc
41
+ proc || raise(ArgumentError, 'block is missing')
42
+ @around_all = proc
43
+ end
44
+ alias around! around_all
45
+
46
+ def around_all?
47
+ @around_all
48
+ end
49
+
50
+ def reset_around_all
51
+ remove_instance_variable(:@around_all)
52
+ end
53
+ end
54
+ end