mspec 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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)