dchelimsky-rspec 1.1.11.1 → 1.1.11.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. data/History.txt +11 -3
  2. data/Manifest.txt +35 -46
  3. data/README.txt +30 -12
  4. data/Rakefile +9 -9
  5. data/{stories/configuration/before_blocks.story → features/configuration/before_blocks.feature} +2 -2
  6. data/{stories/example_groups/autogenerated_docstrings → features/example_groups/autogenerated_docstrings.feature} +1 -1
  7. data/{stories/example_groups/example_group_with_should_methods → features/example_groups/example_group_with_should_methods.feature} +3 -3
  8. data/{stories/example_groups/nested_groups → features/example_groups/nested_groups.feature} +1 -1
  9. data/{stories/example_groups/output → features/example_groups/output.feature} +3 -8
  10. data/{stories/interop/examples_and_tests_together → features/interop/examples_and_tests_together.feature} +5 -4
  11. data/{stories/interop/test_but_not_test_unit → features/interop/test_but_not_test_unit.feature} +2 -2
  12. data/{stories/interop/test_case_with_should_methods → features/interop/test_case_with_should_methods.feature} +2 -2
  13. data/{stories/mock_framework_integration/use_flexmock.story → features/mock_framework_integration/use_flexmock.feature} +1 -1
  14. data/features/step_definitions/running_rspec.rb +48 -0
  15. data/features/support/env.rb +25 -0
  16. data/{stories/resources → features/support}/helpers/cmdline.rb +0 -0
  17. data/{stories/resources → features/support}/helpers/story_helper.rb +0 -0
  18. data/{stories/resources → features/support}/matchers/smart_match.rb +0 -0
  19. data/{plugins → lib/adapters}/mock_frameworks/flexmock.rb +0 -0
  20. data/{plugins → lib/adapters}/mock_frameworks/mocha.rb +0 -0
  21. data/{plugins → lib/adapters}/mock_frameworks/rr.rb +0 -0
  22. data/{plugins → lib/adapters}/mock_frameworks/rspec.rb +1 -1
  23. data/lib/autotest/rspec.rb +3 -2
  24. data/lib/spec/dsl/main.rb +8 -4
  25. data/lib/spec/example/configuration.rb +1 -1
  26. data/lib/spec/example/example_group_factory.rb +6 -2
  27. data/lib/spec/example/example_group_methods.rb +47 -54
  28. data/lib/spec/example/shared_example_group.rb +2 -2
  29. data/lib/spec/example.rb +163 -14
  30. data/lib/spec/interop/test/unit/testresult.rb +1 -1
  31. data/lib/spec/interop/test/unit/testsuite_adapter.rb +1 -1
  32. data/lib/spec/matchers/match_array.rb +75 -0
  33. data/lib/spec/matchers/operator_matcher.rb +34 -7
  34. data/lib/spec/matchers.rb +2 -1
  35. data/lib/spec/mocks/argument_constraints.rb +43 -5
  36. data/lib/spec/runner/example_group_runner.rb +2 -2
  37. data/lib/spec/runner/option_parser.rb +12 -6
  38. data/lib/spec/runner/options.rb +9 -9
  39. data/lib/spec/runner/spec_parser.rb +3 -2
  40. data/lib/spec/runner.rb +54 -188
  41. data/lib/spec/version.rb +1 -1
  42. data/lib/spec.rb +0 -29
  43. data/{rake_tasks → resources/rake}/examples.rake +0 -0
  44. data/{rake_tasks → resources/rake}/examples_with_rcov.rake +0 -0
  45. data/{rake_tasks → resources/rake}/failing_examples_with_html.rake +0 -0
  46. data/{rake_tasks → resources/rake}/verify_rcov.rake +0 -0
  47. data/{stories/resources → resources}/spec/before_blocks_example.rb +1 -1
  48. data/{stories/resources → resources}/spec/example_group_with_should_methods.rb +1 -1
  49. data/{stories/resources → resources}/spec/simple_spec.rb +1 -1
  50. data/resources/spec/spec_with_flexmock.rb +19 -0
  51. data/{stories/resources → resources}/test/spec_and_test_together.rb +1 -1
  52. data/{stories/resources → resources}/test/spec_including_test_but_not_unit.rb +1 -1
  53. data/{stories/resources → resources}/test/test_case_with_should_methods.rb +2 -2
  54. data/rspec.gemspec +5 -4
  55. data/spec/autotest/rspec_spec.rb +2 -1
  56. data/spec/spec/dsl/main_spec.rb +8 -5
  57. data/spec/spec/example/configuration_spec.rb +9 -9
  58. data/spec/spec/example/example_group_factory_spec.rb +22 -3
  59. data/spec/spec/example/example_group_methods_spec.rb +19 -15
  60. data/spec/spec/example/example_group_spec.rb +41 -41
  61. data/spec/spec/example/example_methods_spec.rb +5 -5
  62. data/spec/spec/example/helper_method_spec.rb +24 -0
  63. data/spec/spec/example/pending_module_spec.rb +2 -8
  64. data/spec/spec/example/shared_example_group_spec.rb +5 -5
  65. data/spec/spec/matchers/description_generation_spec.rb +5 -0
  66. data/spec/spec/matchers/handler_spec.rb +7 -7
  67. data/spec/spec/matchers/match_array_spec.rb +83 -0
  68. data/spec/spec/matchers/raise_error_spec.rb +18 -0
  69. data/spec/spec/mocks/hash_including_matcher_spec.rb +39 -2
  70. data/spec/spec/mocks/hash_not_including_matcher_spec.rb +67 -0
  71. data/spec/spec/mocks/mock_spec.rb +5 -6
  72. data/spec/spec/mocks/nil_expectation_warning_spec.rb +1 -1
  73. data/spec/spec/runner/formatter/base_text_formatter_spec.rb +22 -0
  74. data/spec/spec/runner/option_parser_spec.rb +4 -17
  75. data/spec/spec/runner/options_spec.rb +19 -5
  76. data/spec/spec/runner/resources/custom_example_group_runner.rb +14 -0
  77. data/spec/spec/runner/spec_parser_spec.rb +10 -0
  78. data/spec/spec/spec_spec.rb +21 -0
  79. metadata +39 -49
  80. data/lib/spec/adapters/ruby_engine/mri.rb +0 -8
  81. data/lib/spec/adapters/ruby_engine/rubinius.rb +0 -8
  82. data/lib/spec/adapters/ruby_engine.rb +0 -26
  83. data/lib/spec/adapters.rb +0 -1
  84. data/lib/spec/extensions/class.rb +0 -24
  85. data/lib/spec/extensions.rb +0 -1
  86. data/spec/spec/adapters/ruby_engine_spec.rb +0 -16
  87. data/stories/all.rb +0 -5
  88. data/stories/configuration/stories.rb +0 -7
  89. data/stories/example_groups/stories.rb +0 -7
  90. data/stories/helper.rb +0 -6
  91. data/stories/interop/stories.rb +0 -7
  92. data/stories/mock_framework_integration/stories.rb +0 -7
  93. data/stories/pending_stories/README +0 -3
  94. data/stories/resources/spec/spec_with_flexmock.rb +0 -18
  95. data/stories/resources/steps/running_rspec.rb +0 -50
  96. data/stories/resources/stories/failing_story.rb +0 -15
  97. data/stories/stories/multiline_steps.story +0 -23
  98. data/stories/stories/steps/multiline_steps.rb +0 -13
  99. data/stories/stories/stories.rb +0 -6
@@ -13,13 +13,13 @@ module Spec
13
13
  end
14
14
 
15
15
  def self.description_text(*args)
16
- args.inject("") do |result, arg|
17
- result << " " unless (result == "" || arg.to_s =~ /^(\s|\.|#)/)
18
- result << arg.to_s
16
+ args.inject("") do |description, arg|
17
+ description << " " unless (description == "" || arg.to_s =~ /^(\s|\.|#)/)
18
+ description << arg.to_s
19
19
  end
20
20
  end
21
21
 
22
- attr_reader :description_text, :description_options, :spec_path
22
+ attr_reader :description_options, :spec_path
23
23
  alias :options :description_options
24
24
 
25
25
  # Provides the backtrace up to where this example_group was declared.
@@ -42,7 +42,7 @@ WARNING
42
42
 
43
43
  def inherited(klass)
44
44
  super
45
- klass.register
45
+ Spec::Runner.options.add_example_group klass
46
46
  Spec::Runner.register_at_exit_hook
47
47
  end
48
48
 
@@ -61,11 +61,9 @@ WARNING
61
61
  # end
62
62
  #
63
63
  def describe(*args, &example_group_block)
64
- args << {} unless Hash === args.last
65
64
  if example_group_block
65
+ Spec::Example::add_spec_path_to(args)
66
66
  options = args.last
67
- # Ruby 1.9 - the next line uses example_group_block.binding instead of example_group_block
68
- options[:spec_path] = eval("caller(0)[1]", example_group_block.binding) unless options[:spec_path]
69
67
  if options[:shared]
70
68
  create_shared_example_group(*args, &example_group_block)
71
69
  else
@@ -82,12 +80,28 @@ WARNING
82
80
  end
83
81
 
84
82
  def create_subclass(*args, &example_group_block) # :nodoc:
85
- self.subclass("Subclass") do
83
+ subclass("Subclass") do
86
84
  set_description(*args)
87
85
  module_eval(&example_group_block)
88
86
  end
89
87
  end
90
88
 
89
+ # Creates a new subclass of self, with a name "under" our own name.
90
+ # Example:
91
+ #
92
+ # x = Foo::Bar.subclass('Zap'){}
93
+ # x.name # => Foo::Bar::Zap_1
94
+ # x.superclass.name # => Foo::Bar
95
+ def subclass(base_name, &body) # :nodoc:
96
+ @class_count ||= 0
97
+ @class_count += 1
98
+ klass = Class.new(self)
99
+ class_name = "#{base_name}_#{@class_count}"
100
+ const_set(class_name, klass)
101
+ klass.instance_eval(&body)
102
+ klass
103
+ end
104
+
91
105
  # Use this to pull in examples from shared example groups.
92
106
  # See Spec::Runner for information about shared example groups.
93
107
  def it_should_behave_like(*shared_example_groups)
@@ -147,18 +161,18 @@ WARNING
147
161
  alias_method :xit, :xexample
148
162
  alias_method :xspecify, :xexample
149
163
 
150
- def run
151
- examples = examples_to_run
152
- reporter.add_example_group(self) unless examples_to_run.empty?
164
+ def run(run_options)
165
+ examples = examples_to_run(run_options)
166
+ run_options.reporter.add_example_group(self) unless examples.empty?
153
167
  return true if examples.empty?
154
- return dry_run(examples) if dry_run?
168
+ return dry_run(examples, run_options) if run_options.dry_run?
155
169
 
156
170
  plugin_mock_framework
157
171
  define_methods_from_predicate_matchers
158
172
 
159
- success, before_all_instance_variables = run_before_all
160
- success, after_all_instance_variables = execute_examples(success, before_all_instance_variables, examples)
161
- success = run_after_all(success, after_all_instance_variables)
173
+ success, before_all_instance_variables = run_before_all(run_options)
174
+ success, after_all_instance_variables = execute_examples(success, before_all_instance_variables, examples, run_options)
175
+ success = run_after_all(success, after_all_instance_variables, run_options)
162
176
  end
163
177
 
164
178
  def description
@@ -208,10 +222,10 @@ WARNING
208
222
  self
209
223
  end
210
224
 
211
- def examples #:nodoc:
225
+ def examples(run_options=nil) #:nodoc:
212
226
  examples = example_objects.dup
213
227
  add_method_examples(examples)
214
- Spec::Runner.options.reverse ? examples.reverse : examples
228
+ (run_options && run_options.reverse) ? examples.reverse : examples
215
229
  end
216
230
 
217
231
  def number_of_examples #:nodoc:
@@ -226,14 +240,6 @@ WARNING
226
240
  @after_each_parts = nil
227
241
  end
228
242
 
229
- def register
230
- Spec::Runner.options.add_example_group self
231
- end
232
-
233
- def unregister #:nodoc:
234
- Spec::Runner.options.remove_example_group self
235
- end
236
-
237
243
  def run_before_each(example)
238
244
  each_ancestor_example_group_class do |example_group_class|
239
245
  example.eval_each_fail_fast(example_group_class.before_each_parts)
@@ -247,15 +253,14 @@ WARNING
247
253
  end
248
254
 
249
255
  private
250
- def dry_run(examples)
256
+ def dry_run(examples, run_options)
251
257
  examples.each do |example|
252
- Spec::Runner.options.reporter.example_started(example)
253
- Spec::Runner.options.reporter.example_finished(example)
258
+ run_options.reporter.example_started(example)
259
+ run_options.reporter.example_finished(example)
254
260
  end
255
- return true
256
261
  end
257
262
 
258
- def run_before_all
263
+ def run_before_all(run_options)
259
264
  before_all = new("before(:all)")
260
265
  begin
261
266
  each_ancestor_example_group_class do |example_group_class|
@@ -263,23 +268,23 @@ WARNING
263
268
  end
264
269
  return [true, before_all.instance_variable_hash]
265
270
  rescue Exception => e
266
- reporter.failure(before_all, e)
271
+ run_options.reporter.failure(before_all, e)
267
272
  return [false, before_all.instance_variable_hash]
268
273
  end
269
274
  end
270
275
 
271
- def execute_examples(success, instance_variables, examples)
276
+ def execute_examples(success, instance_variables, examples, run_options)
272
277
  return [success, instance_variables] unless success
273
278
 
274
279
  after_all_instance_variables = instance_variables
275
280
  examples.each do |example_group_instance|
276
- success &= example_group_instance.execute(Spec::Runner.options, instance_variables)
281
+ success &= example_group_instance.execute(run_options, instance_variables)
277
282
  after_all_instance_variables = example_group_instance.instance_variable_hash
278
283
  end
279
284
  return [success, after_all_instance_variables]
280
285
  end
281
286
 
282
- def run_after_all(success, instance_variables)
287
+ def run_after_all(success, instance_variables, run_options)
283
288
  after_all = new("after(:all)")
284
289
  after_all.set_instance_variables_from_hash(instance_variables)
285
290
  each_ancestor_example_group_class(:superclass_first) do |example_group_class|
@@ -287,34 +292,22 @@ WARNING
287
292
  end
288
293
  return success
289
294
  rescue Exception => e
290
- reporter.failure(after_all, e)
295
+ run_options.reporter.failure(after_all, e)
291
296
  return false
292
297
  end
293
298
 
294
- def examples_to_run
295
- all_examples = examples
296
- return all_examples unless specified_examples?
299
+ def examples_to_run(run_options)
300
+ all_examples = examples(run_options)
301
+ return all_examples unless specified_examples?(run_options)
297
302
  all_examples.reject do |example|
298
303
  matcher = ExampleGroupMethods.matcher_class.
299
304
  new(description.to_s, example.description)
300
- !matcher.matches?(specified_examples)
305
+ !matcher.matches?(run_options.examples)
301
306
  end
302
307
  end
303
308
 
304
- def specified_examples?
305
- specified_examples && !specified_examples.empty?
306
- end
307
-
308
- def specified_examples
309
- Spec::Runner.options.examples
310
- end
311
-
312
- def reporter
313
- Spec::Runner.options.reporter
314
- end
315
-
316
- def dry_run?
317
- Spec::Runner.options.dry_run
309
+ def specified_examples?(run_options)
310
+ run_options.examples && !run_options.examples.empty?
318
311
  end
319
312
 
320
313
  def example_objects
@@ -34,11 +34,11 @@ module Spec
34
34
  existing_example_group = find(new_example_group.description)
35
35
  return false unless existing_example_group
36
36
  return true if new_example_group.equal?(existing_example_group)
37
- return true if spec_path(new_example_group) == spec_path(existing_example_group)
37
+ return true if expanded_path(new_example_group) == expanded_path(existing_example_group)
38
38
  raise ArgumentError.new("Shared Example '#{existing_example_group.description}' already exists")
39
39
  end
40
40
 
41
- def spec_path(example_group)
41
+ def expanded_path(example_group)
42
42
  File.expand_path(example_group.spec_path)
43
43
  end
44
44
  end
data/lib/spec/example.rb CHANGED
@@ -1,24 +1,173 @@
1
1
  module Spec
2
+ # == Example Groups and Code Examples
3
+ #
4
+ # A Code Example is an executable example of how a bit of code is expected
5
+ # to behave.
6
+ #
7
+ # An Example Group is a group of code examples.
8
+ #
9
+ # RSpec exposes a DSL to describe groups of examples.
10
+ #
11
+ # describe Account do
12
+ # it "should have a balance of $0" do
13
+ # account = Account.new
14
+ # account.balance.should == Money.new(0, :dollars)
15
+ # end
16
+ # end
17
+ #
18
+ # == Before and After
19
+ #
20
+ # You can use the <tt>before()</tt> and <tt>after()</tt> methods to extract
21
+ # common code within an Example Group. Both methods take an optional scope
22
+ # argument so you can run the block before :each example or before :all
23
+ # examples
24
+ #
25
+ # describe "..." do
26
+ # before :all do
27
+ # ...
28
+ # end
29
+ #
30
+ # before :each do
31
+ # ...
32
+ # end
33
+ #
34
+ # it "should do something" do
35
+ # ...
36
+ # end
37
+ #
38
+ # it "should do something else" do
39
+ # ...
40
+ # end
41
+ #
42
+ # after :each do
43
+ # ...
44
+ # end
45
+ #
46
+ # after :all do
47
+ # ...
48
+ # end
49
+ #
50
+ # end
51
+ #
52
+ # The <tt>before :each</tt> block will run before each of the examples, once
53
+ # for each example. Likewise, the <tt>after :each</tt> block will run after
54
+ # each of the examples.
55
+ #
56
+ # It is also possible to specify a <tt>before :all</tt> and <tt>after
57
+ # :all</tt> block that will run only once for each example group, before the
58
+ # first <code>before :each</code> and after the last <code>after
59
+ # :each</code> respectively. The use of these is generally discouraged,
60
+ # because it introduces dependencies between the examples. Still, it might
61
+ # prove useful for very expensive operations if you know what you are doing.
62
+ #
63
+ # == Local helper methods
64
+ #
65
+ # You can include local helper methods by simply expressing them within an
66
+ # example group:
67
+ #
68
+ # describe "..." do
69
+ #
70
+ # it "..." do
71
+ # helper_method
72
+ # end
73
+ #
74
+ # def helper_method
75
+ # ...
76
+ # end
77
+ #
78
+ # end
79
+ #
80
+ # == Included helper methods
81
+ #
82
+ # You can include helper methods in multiple example groups by expressing
83
+ # them within a module, and then including that module in your example
84
+ # groups:
85
+ #
86
+ # module AccountExampleHelperMethods
87
+ # def helper_method
88
+ # ...
89
+ # end
90
+ # end
91
+ #
92
+ # describe "A new account" do
93
+ # include AccountExampleHelperMethods
94
+ # before do
95
+ # @account = Account.new
96
+ # end
97
+ #
98
+ # it "should have a balance of $0" do
99
+ # helper_method
100
+ # @account.balance.should eql(Money.new(0, :dollars))
101
+ # end
102
+ # end
103
+ #
104
+ # == Shared Example Groups
105
+ #
106
+ # You can define a shared example group, that may be used on other groups
107
+ #
108
+ # share_examples_for "All Editions" do
109
+ # it "all editions behaviour" ...
110
+ # end
111
+ #
112
+ # describe SmallEdition do
113
+ # it_should_behave_like "All Editions"
114
+ #
115
+ # it "should do small edition stuff" do
116
+ # ...
117
+ # end
118
+ # end
119
+ #
120
+ # You can also assign the shared group to a module and include that
121
+ #
122
+ # share_as :AllEditions do
123
+ # it "should do all editions stuff" ...
124
+ # end
125
+ #
126
+ # describe SmallEdition do
127
+ # it_should_behave_like AllEditions
128
+ #
129
+ # it "should do small edition stuff" do
130
+ # ...
131
+ # end
132
+ # end
133
+ #
134
+ # And, for those of you who prefer to use something more like Ruby, you can
135
+ # just include the module directly
136
+ #
137
+ # describe SmallEdition do
138
+ # include AllEditions
139
+ #
140
+ # it "should do small edition stuff" do
141
+ # ...
142
+ # end
143
+ # end
2
144
  module Example
3
- def self.args_and_options(*args)
4
- with_options_from(args) do |options|
5
- return args, options
145
+ class << self
146
+ def args_and_options(*args) # :nodoc:
147
+ with_options_from(args) do |options|
148
+ return args, options
149
+ end
6
150
  end
7
- end
8
151
 
9
- def self.scope_from(*args)
10
- args[0] || :each
11
- end
152
+ def scope_from(*args) # :nodoc:
153
+ args[0] || :each
154
+ end
12
155
 
13
- def self.scope_and_options(*args)
14
- args, options = args_and_options(*args)
15
- return scope_from(*args), options
16
- end
156
+ def scope_and_options(*args) # :nodoc:
157
+ args, options = args_and_options(*args)
158
+ return scope_from(*args), options
159
+ end
160
+
161
+ def add_spec_path_to(args) # :nodoc:
162
+ args << {} unless Hash === args.last
163
+ args.last[:spec_path] ||= caller(0)[2]
164
+ end
17
165
 
18
- private
166
+ private
19
167
 
20
- def self.with_options_from(args)
21
- yield Hash === args.last ? args.pop : {} if block_given?
168
+ def with_options_from(args)
169
+ yield Hash === args.last ? args.pop : {} if block_given?
170
+ end
22
171
  end
23
172
  end
24
173
  end
@@ -1,6 +1,6 @@
1
1
  class Test::Unit::TestResult
2
2
  alias_method :tu_passed?, :passed?
3
3
  def passed?
4
- return tu_passed? & ::Spec.run
4
+ return tu_passed? & ::Spec::Runner.run
5
5
  end
6
6
  end
@@ -14,7 +14,7 @@ module Test
14
14
 
15
15
  def run(*args)
16
16
  return true unless args.empty?
17
- example_group.run
17
+ example_group.run(Spec::Runner.options)
18
18
  end
19
19
 
20
20
  def size
@@ -0,0 +1,75 @@
1
+ module Spec
2
+ module Matchers
3
+
4
+ class MatchArray #:nodoc:
5
+
6
+ def initialize(expected)
7
+ @expected = expected
8
+ end
9
+
10
+ def matches?(actual)
11
+ @actual = actual
12
+ @extra_items = difference_between_arrays(@actual, @expected)
13
+ @missing_items = difference_between_arrays(@expected, @actual)
14
+ @extra_items.empty? && @missing_items.empty?
15
+ end
16
+
17
+ def failure_message
18
+ message = "expected collection contained: #{@expected.sort.inspect}\n"
19
+ message += "actual collection contained: #{@actual.sort.inspect}\n"
20
+ message += "the missing elements were: #{@missing_items.sort.inspect}\n" unless @missing_items.empty?
21
+ message += "the extra elements were: #{@extra_items.sort.inspect}\n" unless @extra_items.empty?
22
+ message
23
+ end
24
+
25
+ def description
26
+ "contain exactly #{_pretty_print(@expected)}"
27
+ end
28
+
29
+ private
30
+
31
+ def difference_between_arrays(array_1, array_2)
32
+ difference = array_1.dup
33
+ array_2.each do |element|
34
+ if index = difference.index(element)
35
+ difference.delete_at(index)
36
+ end
37
+ end
38
+ difference
39
+ end
40
+
41
+ def _pretty_print(array)
42
+ result = ""
43
+ array.each_with_index do |item, index|
44
+ if index < (array.length - 2)
45
+ result << "#{item.inspect}, "
46
+ elsif index < (array.length - 1)
47
+ result << "#{item.inspect} and "
48
+ else
49
+ result << "#{item.inspect}"
50
+ end
51
+ end
52
+ result
53
+ end
54
+
55
+ end
56
+
57
+ # :call-seq:
58
+ # should =~ expected
59
+ #
60
+ # Passes if actual contains all of the expected regardless of order.
61
+ # This works for collections. Pass in multiple args and it will only
62
+ # pass if all args are found in collection.
63
+ #
64
+ # NOTE: there is no should_not version of array.should =~ other_array
65
+ #
66
+ # == Examples
67
+ #
68
+ # [1,2,3].should =~ [1,2,3] # => would pass
69
+ # [1,2,3].should =~ [2,3,1] # => would pass
70
+ # [1,2,3,4].should =~ [1,2,3] # => would fail
71
+ # [1,2,2,3].should =~ [1,2,3] # => would fail
72
+ # [1,2,3].should =~ [1,2,3,4] # => would fail
73
+ OperatorMatcher.register(Array, '=~', Spec::Matchers::MatchArray)
74
+ end
75
+ end
@@ -1,22 +1,43 @@
1
1
  module Spec
2
2
  module Matchers
3
+
3
4
  class OperatorMatcher
5
+ @operator_registry = {}
6
+
7
+ def self.register(klass, operator, matcher)
8
+ @operator_registry[klass] ||= {}
9
+ @operator_registry[klass][operator] = matcher
10
+ end
11
+
12
+ def self.get(klass, operator)
13
+ return @operator_registry[klass][operator] if @operator_registry[klass]
14
+ nil
15
+ end
16
+
4
17
  def initialize(actual)
5
18
  @actual = actual
6
19
  end
7
-
8
- ['==','===','<','<=','>=','>','=~'].each do |operator|
9
- define_method operator do |expected|
10
- ::Spec::Matchers.last_matcher = self
11
- @operator, @expected = operator, expected
12
- __delegate_operator(@actual, operator, expected)
20
+
21
+ def self.use_custom_matcher_or_delegate(operator)
22
+ define_method(operator) do |expected|
23
+ if matcher = OperatorMatcher.get(@actual.class, operator)
24
+ return @actual.send(matcher_method, matcher.new(expected))
25
+ else
26
+ ::Spec::Matchers.last_matcher = self
27
+ @operator, @expected = operator, expected
28
+ __delegate_operator(@actual, operator, expected)
29
+ end
13
30
  end
14
31
  end
15
32
 
33
+ ['==', '===', '=~', '>', '>=', '<', '<='].each do |operator|
34
+ use_custom_matcher_or_delegate operator
35
+ end
36
+
16
37
  def fail_with_message(message)
17
38
  Spec::Expectations.fail_with(message, @expected, @actual)
18
39
  end
19
-
40
+
20
41
  def description
21
42
  "#{@operator} #{@expected.inspect}"
22
43
  end
@@ -24,6 +45,9 @@ module Spec
24
45
  end
25
46
 
26
47
  class PositiveOperatorMatcher < OperatorMatcher #:nodoc:
48
+ def matcher_method
49
+ :should
50
+ end
27
51
 
28
52
  def __delegate_operator(actual, operator, expected)
29
53
  return true if actual.__send__(operator, expected)
@@ -37,6 +61,9 @@ module Spec
37
61
  end
38
62
 
39
63
  class NegativeOperatorMatcher < OperatorMatcher #:nodoc:
64
+ def matcher_method
65
+ :should_not
66
+ end
40
67
 
41
68
  def __delegate_operator(actual, operator, expected)
42
69
  return true unless actual.__send__(operator, expected)
data/lib/spec/matchers.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'spec/matchers/operator_matcher'
1
2
  require 'spec/matchers/generated_descriptions'
2
3
  require 'spec/matchers/errors'
3
4
  require 'spec/matchers/method_missing'
@@ -5,6 +6,7 @@ require 'spec/matchers/simple_matcher'
5
6
  require 'spec/matchers/be'
6
7
  require 'spec/matchers/be_close'
7
8
  require 'spec/matchers/change'
9
+ require 'spec/matchers/match_array'
8
10
  require 'spec/matchers/eql'
9
11
  require 'spec/matchers/equal'
10
12
  require 'spec/matchers/exist'
@@ -16,7 +18,6 @@ require 'spec/matchers/raise_error'
16
18
  require 'spec/matchers/respond_to'
17
19
  require 'spec/matchers/satisfy'
18
20
  require 'spec/matchers/throw_symbol'
19
- require 'spec/matchers/operator_matcher'
20
21
 
21
22
  module Spec
22
23
 
@@ -73,6 +73,25 @@ module Spec
73
73
  end
74
74
  end
75
75
 
76
+ class HashNotIncludingConstraint
77
+ def initialize(expected)
78
+ @expected = expected
79
+ end
80
+
81
+ def ==(actual)
82
+ @expected.each do | key, value |
83
+ return false if actual.has_key?(key) && value == actual[key]
84
+ end
85
+ true
86
+ rescue NoMethodError => ex
87
+ return false
88
+ end
89
+
90
+ def description
91
+ "hash_not_including(#{@expected.inspect.sub(/^\{/,"").sub(/\}$/,"")})"
92
+ end
93
+ end
94
+
76
95
  class DuckTypeConstraint
77
96
  def initialize(*methods_to_respond_to)
78
97
  @methods_to_respond_to = methods_to_respond_to
@@ -153,12 +172,31 @@ module Spec
153
172
  end
154
173
 
155
174
  # :call-seq:
156
- # object.should_receive(:message).with(hash_including(:this => that))
157
- #
158
- # Passes if the argument is a hash that includes the specified key/value
175
+ # object.should_receive(:message).with(hash_including(:key => val))
176
+ # object.should_receive(:message).with(hash_including(:key))
177
+ # object.should_receive(:message).with(hash_including(:key, :key2 => val2))
178
+ # Passes if the argument is a hash that includes the specified key(s) or key/value
159
179
  # pairs. If the hash includes other keys, it will still pass.
160
- def hash_including(expected={})
161
- HashIncludingConstraint.new(expected)
180
+ def hash_including(*args)
181
+ HashIncludingConstraint.new(anythingize_lonely_keys(*args))
182
+ end
183
+
184
+ # :call-seq:
185
+ # object.should_receive(:message).with(hash_not_including(:key => val))
186
+ # object.should_receive(:message).with(hash_not_including(:key))
187
+ # object.should_receive(:message).with(hash_not_including(:key, :key2 => :val2))
188
+ #
189
+ # Passes if the argument is a hash that doesn't include the specified key(s) or key/value
190
+ def hash_not_including(*args)
191
+ HashNotIncludingConstraint.new(anythingize_lonely_keys(*args))
192
+ end
193
+
194
+ private
195
+
196
+ def anythingize_lonely_keys(*args)
197
+ hash = args.last.class == Hash ? args.delete_at(-1) : {}
198
+ args.each { | arg | hash[arg] = anything }
199
+ hash
162
200
  end
163
201
  end
164
202
  end
@@ -15,11 +15,11 @@ module Spec
15
15
  end
16
16
  end
17
17
 
18
- def run
18
+ def run(run_options)
19
19
  prepare
20
20
  success = true
21
21
  example_groups.each do |example_group|
22
- success = success & example_group.run
22
+ success = success & example_group.run(run_options)
23
23
  end
24
24
  return success
25
25
  ensure