mspec 1.0.0 → 1.1.0

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
@@ -9,7 +9,7 @@ task :default => :spec
9
9
 
10
10
  spec = Gem::Specification.new do |s|
11
11
  s.name = %q{mspec}
12
- s.version = "1.0.0"
12
+ s.version = "1.1.0"
13
13
 
14
14
  s.specification_version = 2 if s.respond_to? :specification_version=
15
15
 
@@ -25,20 +25,20 @@ spec = Gem::Specification.new do |s|
25
25
  s.rubyforge_project = 'http://rubyforge.org/projects/mspec'
26
26
  s.require_paths = ["lib"]
27
27
  s.rubygems_version = %q{1.1.1}
28
- s.summary = <<EOS
29
- MSpec is a specialized framework that is syntax-compatible
30
- with RSpec for basic things like describe, it blocks and
28
+ s.summary = <<EOS
29
+ MSpec is a specialized framework that is syntax-compatible
30
+ with RSpec for basic things like describe, it blocks and
31
31
  before, after actions.
32
32
 
33
- MSpec contains additional features that assist in writing
34
- the RubySpecs used by multiple Ruby implementations. Also,
35
- MSpec attempts to use the simplest Ruby language features
33
+ MSpec contains additional features that assist in writing
34
+ the RubySpecs used by multiple Ruby implementations. Also,
35
+ MSpec attempts to use the simplest Ruby language features
36
36
  so that beginning Ruby implementations can run it.
37
37
  EOS
38
-
38
+
39
39
  s.rdoc_options << '--title' << 'MSpec Gem' <<
40
40
  '--main' << 'README' <<
41
41
  '--line-numbers'
42
42
  end
43
43
 
44
- Rake::GemPackageTask.new(spec){ |pkg| pkg.gem_spec = spec }
44
+ Rake::GemPackageTask.new(spec){ |pkg| pkg.gem_spec = spec }
data/lib/mspec.rb CHANGED
@@ -4,3 +4,8 @@ require 'mspec/mocks'
4
4
  require 'mspec/runner'
5
5
  require 'mspec/guards'
6
6
  require 'mspec/helpers'
7
+
8
+ # If the implementation on which the specs are run cannot
9
+ # load pp from the standard library, add a pp.rb file that
10
+ # defines the #pretty_inspect method on Object or Kernel.
11
+ require 'pp'
@@ -20,7 +20,6 @@ class MSpecCI < MSpecScript
20
20
  options.separator "\n How to run the specs"
21
21
  options.add_config { |f| load f }
22
22
  options.add_name
23
- options.add_tags_dir
24
23
  options.add_pretend
25
24
  options.add_interrupt
26
25
 
@@ -59,7 +58,7 @@ class MSpecCI < MSpecScript
59
58
  files.concat(Dir[item+"/**/*_spec.rb"].sort) if stat.directory?
60
59
  end
61
60
 
62
- MSpec.register_tags_path config[:tags_dir]
61
+ MSpec.register_tags_patterns config[:tags_patterns]
63
62
  MSpec.register_files files
64
63
  TagFilter.new(:exclude, "fails").register
65
64
  TagFilter.new(:exclude, "unstable").register
@@ -24,7 +24,6 @@ class MSpecRun < MSpecScript
24
24
  options.separator "\n How to modify the execution"
25
25
  options.add_config { |f| load f }
26
26
  options.add_name
27
- options.add_tags_dir
28
27
  options.add_randomize
29
28
  options.add_pretend
30
29
  options.add_interrupt
@@ -70,7 +69,7 @@ class MSpecRun < MSpecScript
70
69
  files.concat(Dir[item+"/**/*_spec.rb"].sort) if stat.directory?
71
70
  end
72
71
 
73
- MSpec.register_tags_path config[:tags_dir]
72
+ MSpec.register_tags_patterns config[:tags_patterns]
74
73
  MSpec.register_files files
75
74
 
76
75
  MSpec.process
@@ -30,7 +30,6 @@ class MSpecTag < MSpecScript
30
30
  options.separator "\n How to modify the execution"
31
31
  options.add_config { |f| load f }
32
32
  options.add_name
33
- options.add_tags_dir
34
33
  options.add_pretend
35
34
  options.add_interrupt
36
35
 
@@ -77,7 +76,7 @@ class MSpecTag < MSpecScript
77
76
  files.concat(Dir[item+"/**/*_spec.rb"].sort) if stat.directory?
78
77
  end
79
78
 
80
- MSpec.register_tags_path config[:tags_dir]
79
+ MSpec.register_tags_patterns config[:tags_patterns]
81
80
  MSpec.register_files files
82
81
 
83
82
  MSpec.process
@@ -1,5 +1,9 @@
1
1
  module Kernel
2
2
  def const_lookup(c)
3
- c.split('::').inject(Object) { |k,n| k.const_get n }
3
+ names = c.split '::'
4
+ names.shift if names.first.empty?
5
+ names.inject(Object) do |m, n|
6
+ m.const_defined?(n) ? m.const_get(n) : m.const_missing(n)
7
+ end
4
8
  end
5
9
  end
@@ -9,6 +9,7 @@ require 'mspec/matchers/be_true'
9
9
  require 'mspec/matchers/equal'
10
10
  require 'mspec/matchers/eql'
11
11
  require 'mspec/matchers/include'
12
+ require 'mspec/matchers/match_yaml'
12
13
  require 'mspec/matchers/raise_error'
13
14
  require 'mspec/matchers/output'
14
15
  require 'mspec/matchers/output_to_fd'
@@ -0,0 +1,47 @@
1
+ class MatchYAMLMatcher
2
+
3
+ def initialize(expected)
4
+ if valid_yaml?(expected)
5
+ @expected = expected
6
+ else
7
+ @expected = expected.to_yaml
8
+ end
9
+ end
10
+
11
+ def matches?(actual)
12
+ @actual = actual
13
+ clean_yaml(@actual) == @expected
14
+ end
15
+
16
+ def failure_message
17
+ ["Expected #{@actual.inspect}", " to match #{@expected.inspect}"]
18
+ end
19
+
20
+ def negative_failure_message
21
+ ["Expected #{@actual.inspect}", " to match #{@expected.inspect}"]
22
+ end
23
+
24
+ protected
25
+
26
+ def clean_yaml(yaml)
27
+ yaml.gsub(/([^-])\s+\n/, "\\1\n")
28
+ end
29
+
30
+ def valid_yaml?(obj)
31
+ require 'yaml'
32
+ begin
33
+ YAML.load(obj)
34
+ rescue
35
+ false
36
+ else
37
+ true
38
+ end
39
+ end
40
+ end
41
+
42
+ class Object
43
+ def match_yaml(expected)
44
+ MatchYAMLMatcher.new(expected)
45
+ end
46
+ end
47
+
@@ -2,22 +2,35 @@ require 'mspec/expectations/expectations'
2
2
 
3
3
  module Mock
4
4
  def self.reset
5
- @expects = nil
5
+ @mocks = @stubs = nil
6
6
  end
7
7
 
8
- def self.expects
9
- @expects ||= Hash.new { |h,k| h[k] = [] }
8
+ def self.mocks
9
+ @mocks ||= Hash.new { |h,k| h[k] = [] }
10
10
  end
11
11
 
12
- def self.replaced_name(sym)
13
- :"__ms_#{sym}__"
12
+ def self.stubs
13
+ @stubs ||= Hash.new { |h,k| h[k] = [] }
14
14
  end
15
15
 
16
- def self.install_method(obj, sym, type = :mock)
16
+ def self.replaced_name(obj, sym)
17
+ :"__ms_#{obj.__id__}_#{sym}__"
18
+ end
19
+
20
+ def self.replaced_key(obj, sym)
21
+ [replaced_name(obj, sym), obj, sym]
22
+ end
23
+
24
+ def self.replaced?(key)
25
+ !!(mocks.keys + stubs.keys).find { |k| k.first == key.first }
26
+ end
27
+
28
+ def self.install_method(obj, sym, type=nil)
17
29
  meta = class << obj; self; end
18
30
 
19
- if (sym.to_sym == :respond_to? or obj.respond_to?(sym)) and !meta.instance_methods.include?(replaced_name(sym).to_s)
20
- meta.__send__ :alias_method, replaced_name(sym), sym.to_sym
31
+ key = replaced_key obj, sym
32
+ if (sym.to_sym == :respond_to? or obj.respond_to?(sym)) and !replaced?(key)
33
+ meta.__send__ :alias_method, key.first, sym.to_sym
21
34
  end
22
35
 
23
36
  meta.class_eval <<-END
@@ -26,23 +39,26 @@ module Mock
26
39
  end
27
40
  END
28
41
 
29
- MSpec.actions :expectation, MSpec.current.state
42
+ proxy = MockProxy.new type
30
43
 
31
- proxy = MockProxy.new
44
+ MSpec.actions :expectation, MSpec.current.state if proxy.mock?
32
45
 
33
- if type == :stub
34
- expects[[obj, sym]] << proxy
35
- proxy.at_least(0)
46
+ if proxy.stub?
47
+ stubs[key].unshift proxy
36
48
  else
37
- expects[[obj, sym]].unshift proxy
38
- proxy.exactly(1)
49
+ mocks[key] << proxy
39
50
  end
51
+
52
+ proxy
40
53
  end
41
54
 
42
- def self.verify_count
43
- expects.each do |key, proxies|
44
- obj, sym = key.first, key.last
55
+ def self.name_or_inspect(obj)
56
+ obj.instance_variable_get(:@name) || obj.inspect
57
+ end
45
58
 
59
+ def self.verify_count
60
+ mocks.each do |key, proxies|
61
+ replaced, obj, sym = *key
46
62
  proxies.each do |proxy|
47
63
  qualifier, count = proxy.count
48
64
  pass = case qualifier
@@ -52,12 +68,16 @@ module Mock
52
68
  proxy.calls <= count
53
69
  when :exactly
54
70
  proxy.calls == count
71
+ when :any_number_of_times
72
+ true
55
73
  else
56
74
  false
57
75
  end
58
76
  unless pass
59
- Expectation.fail_with("Mock #{obj.inspect}\nexpected to receive #{sym} #{qualifier.to_s.sub('_', ' ')} #{count} times",
60
- "but received it #{proxy.calls} times")
77
+ Expectation.fail_with(
78
+ "Mock '#{name_or_inspect obj}' expected to receive '#{sym}' " \
79
+ "#{qualifier.to_s.sub('_', ' ')} #{count} times",
80
+ "but received it #{proxy.calls} times")
61
81
  end
62
82
  end
63
83
  end
@@ -66,7 +86,9 @@ module Mock
66
86
  def self.verify_call(obj, sym, *args, &block)
67
87
  compare = *args
68
88
 
69
- expects[[obj, sym]].each do |proxy|
89
+ key = replaced_key obj, sym
90
+ proxies = mocks[key] + stubs[key]
91
+ proxies.each do |proxy|
70
92
  pass = case proxy.arguments
71
93
  when :any_args
72
94
  true
@@ -75,20 +97,24 @@ module Mock
75
97
  else
76
98
  proxy.arguments == compare
77
99
  end
78
-
100
+
79
101
  if proxy.yielding?
80
102
  if block
81
103
  proxy.yielding.each do |args_to_yield|
82
104
  if block.arity == -1 || block.arity == args_to_yield.size
83
105
  block.call(*args_to_yield)
84
106
  else
85
- Expectation.fail_with("Mock #{obj.inspect} asked to yield |#{proxy.yielding.join(', ')}| on #{sym}\n",
86
- "but a block with arity #{block.arity} was passed")
107
+ Expectation.fail_with(
108
+ "Mock '#{name_or_inspect obj}' asked to yield " \
109
+ "|#{proxy.yielding.join(', ')}| on #{sym}\n",
110
+ "but a block with arity #{block.arity} was passed")
87
111
  end
88
112
  end
89
113
  else
90
- Expectation.fail_with("Mock #{obj.inspect} asked to yield |[#{proxy.yielding.join('], [')}]| on #{sym}\n",
91
- "but no block was passed")
114
+ Expectation.fail_with(
115
+ "Mock '#{name_or_inspect obj}' asked to yield " \
116
+ "|[#{proxy.yielding.join('], [')}]| on #{sym}\n",
117
+ "but no block was passed")
92
118
  end
93
119
  end
94
120
 
@@ -99,18 +125,18 @@ module Mock
99
125
  end
100
126
 
101
127
  if sym.to_sym == :respond_to?
102
- return obj.__send__(replaced_name(sym), compare)
128
+ return obj.__send__(replaced_name(obj, sym), compare)
103
129
  else
104
- Expectation.fail_with("Mock #{obj.inspect}: method #{sym}\n",
130
+ Expectation.fail_with("Mock '#{name_or_inspect obj}': method #{sym}\n",
105
131
  "called with unexpected arguments (#{Array(compare).join(' ')})")
106
132
  end
107
133
  end
108
134
 
109
135
  def self.cleanup
110
- expects.keys.each do |obj, sym|
136
+ symbols = mocks.keys + stubs.keys
137
+ symbols.uniq.each do |replaced, obj, sym|
111
138
  meta = class << obj; self; end
112
139
 
113
- replaced = replaced_name(sym)
114
140
  if meta.instance_methods.include?(replaced.to_s)
115
141
  meta.__send__ :alias_method, sym.to_sym, replaced
116
142
  meta.__send__ :remove_method, replaced
@@ -1,16 +1,10 @@
1
1
  require 'mspec/mocks/proxy'
2
2
 
3
- class MockObject
4
- def initialize(name)
5
- @name = name
6
- end
7
- end
8
-
9
3
  class Object
10
4
  def stub!(sym)
11
5
  Mock.install_method self, sym, :stub
12
6
  end
13
-
7
+
14
8
  def should_receive(sym)
15
9
  Mock.install_method self, sym
16
10
  end
@@ -18,11 +12,9 @@ class Object
18
12
  def should_not_receive(sym)
19
13
  proxy = Mock.install_method self, sym
20
14
  proxy.exactly(0).times
21
- # return nil so that further chained calls will raise
22
- nil
23
15
  end
24
16
 
25
- def mock(name)
26
- MockObject.new(name)
17
+ def mock(name, options={})
18
+ MockObject.new name, options
27
19
  end
28
20
  end
@@ -1,13 +1,33 @@
1
+ class MockObject
2
+ def initialize(name, options={})
3
+ @name = name
4
+ @null = options[:null_object]
5
+ end
6
+
7
+ def method_missing(sym, *args, &block)
8
+ @null ? self : super
9
+ end
10
+ end
11
+
1
12
  class MockProxy
2
- def initialize
13
+ def initialize(type=nil)
3
14
  @multiple_returns = nil
4
15
  @returning = nil
5
16
  @yielding = []
6
17
  @arguments = :any_args
18
+ @type = type || :mock
19
+ end
20
+
21
+ def mock?
22
+ @type == :mock
23
+ end
24
+
25
+ def stub?
26
+ @type == :stub
7
27
  end
8
28
 
9
29
  def count
10
- @count ||= [:exactly, 0]
30
+ @count ||= mock? ? [:exactly, 1] : [:any_number_of_times, 0]
11
31
  end
12
32
 
13
33
  def arguments
@@ -61,7 +81,8 @@ class MockProxy
61
81
  end
62
82
 
63
83
  def any_number_of_times
64
- at_least 0
84
+ @count = [:any_number_of_times, 0]
85
+ self
65
86
  end
66
87
 
67
88
  def with(*args)
@@ -1,4 +1,3 @@
1
-
2
1
  require 'mspec/runner/state'
3
2
  require 'mspec/runner/tag'
4
3
  require 'fileutils'
@@ -41,6 +40,9 @@ module MSpec
41
40
 
42
41
  shuffle files if randomize?
43
42
  files.each do |file|
43
+ @env = Object.new
44
+ @env.extend MSpec
45
+
44
46
  store :file, file
45
47
  actions :load
46
48
  protect("loading #{file}") { Kernel.load file }
@@ -65,8 +67,15 @@ module MSpec
65
67
  store :files, files
66
68
  end
67
69
 
68
- def self.register_tags_path(path)
69
- store :tags_path, path
70
+ # Stores one or more substitution patterns for transforming
71
+ # a spec filename into a tags filename, where each pattern
72
+ # has the form:
73
+ #
74
+ # [Regexp, String]
75
+ #
76
+ # See also +tags_file+.
77
+ def self.register_tags_patterns(patterns)
78
+ store :tags_patterns, patterns
70
79
  end
71
80
 
72
81
  def self.register_mode(mode)
@@ -118,7 +127,7 @@ module MSpec
118
127
 
119
128
  def self.protect(msg, &block)
120
129
  begin
121
- instance_eval(&block)
130
+ @env.instance_eval(&block)
122
131
  rescue Exception => e
123
132
  register_exit 1
124
133
  if current and current.state
@@ -168,18 +177,22 @@ module MSpec
168
177
  end
169
178
  end
170
179
 
171
- def self.tags_path
172
- retrieve(:tags_path) || "spec/tags"
173
- end
174
-
180
+ # Transforms a spec filename into a tags filename by applying each
181
+ # substitution pattern in :tags_pattern. The default patterns are:
182
+ #
183
+ # [%r(/spec/), '/spec/tags/'], [/_spec.rb$/, '_tags.txt']
184
+ #
185
+ # which will perform the following transformation:
186
+ #
187
+ # path/to/spec/class/method_spec.rb => path/to/spec/tags/class/method_tags.txt
188
+ #
189
+ # See also +register_tags_patterns+.
175
190
  def self.tags_file
176
- path = tags_path
177
- file = retrieve :file
178
- tags_file = File.basename(file, '.*').sub(/_spec$/, '_tags') + '.txt'
179
-
180
- m = file.match %r[.*spec/(.*)/.*_spec.rb]
181
- path = File.join(path, m[1]) if m
182
- File.join path, tags_file
191
+ patterns = retrieve(:tags_patterns) ||
192
+ [[%r(spec/), 'spec/tags/'], [/_spec.rb$/, '_tags.txt']]
193
+ patterns.inject(retrieve(:file).dup) do |file, pattern|
194
+ file.gsub *pattern
195
+ end
183
196
  end
184
197
 
185
198
  def self.read_tags(*keys)