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