File: shared_example_group.rb

package info (click to toggle)
ruby-rspec-core 2.14.7-2
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 1,756 kB
  • ctags: 1,195
  • sloc: ruby: 12,708; makefile: 14
file content (185 lines) | stat: -rw-r--r-- 6,011 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
module RSpec
  module Core
    module SharedExampleGroup
      # @overload shared_examples(name, &block)
      # @overload shared_examples(name, tags, &block)
      #
      # Wraps the `block` in a module which can then be included in example
      # groups using `include_examples`, `include_context`, or
      # `it_behaves_like`.
      #
      # @param [String] name to match when looking up this shared group
      # @param block to be eval'd in a nested example group generated by `it_behaves_like`
      #
      # @example
      #
      #   shared_examples "auditable" do
      #     it "stores an audit record on save!" do
      #       lambda { auditable.save! }.should change(Audit, :count).by(1)
      #     end
      #   end
      #
      #   class Account do
      #     it_behaves_like "auditable" do
      #       def auditable; Account.new; end
      #     end
      #   end
      #
      # @see ExampleGroup.it_behaves_like
      # @see ExampleGroup.include_examples
      # @see ExampleGroup.include_context
      def shared_examples(*args, &block)
        SharedExampleGroup.registry.add_group(self, *args, &block)
      end

      alias_method :shared_context,      :shared_examples
      alias_method :share_examples_for,  :shared_examples
      alias_method :shared_examples_for, :shared_examples

      # @deprecated
      def share_as(name, &block)
        RSpec.deprecate("Rspec::Core::SharedExampleGroup#share_as",
                        :replacement => "RSpec::SharedContext or shared_examples")
        SharedExampleGroup.registry.add_const(self, name, &block)
      end

      def shared_example_groups
        SharedExampleGroup.registry.shared_example_groups_for('main', *ancestors[0..-1])
      end

      module TopLevelDSL
        def shared_examples(*args, &block)
          SharedExampleGroup.registry.add_group('main', *args, &block)
        end

        alias_method :shared_context,      :shared_examples
        alias_method :share_examples_for,  :shared_examples
        alias_method :shared_examples_for, :shared_examples

        def share_as(name, &block)
          RSpec.deprecate("Rspec::Core::SharedExampleGroup#share_as",
                          :replacement => "RSpec::SharedContext or shared_examples")
          SharedExampleGroup.registry.add_const('main', name, &block)
        end

        def shared_example_groups
          SharedExampleGroup.registry.shared_example_groups_for('main')
        end
      end

      def self.registry
        @registry ||= Registry.new
      end

      # @private
      #
      # Used internally to manage the shared example groups and
      # constants. We want to limit the number of methods we add
      # to objects we don't own (main and Module) so this allows
      # us to have helper methods that don't get added to those
      # objects.
      class Registry
        def add_group(source, *args, &block)
          ensure_block_has_source_location(block, caller[1])

          if key? args.first
            key = args.shift
            warn_if_key_taken source, key, block
            add_shared_example_group source, key, block
          end

          unless args.empty?
            mod = Module.new
            (class << mod; self; end).send :define_method, :extended  do |host|
              host.class_eval(&block)
            end
            RSpec.configuration.extend mod, *args
          end
        end

        def add_const(source, name, &block)
          if Object.const_defined?(name)
            mod = Object.const_get(name)
            raise_name_error unless mod.created_from_caller(caller)
          end

          mod = Module.new do
            @shared_block = block
            @caller_line = caller.last

            def self.created_from_caller(other_caller)
              @caller_line == other_caller.last
            end

            def self.included(kls)
              kls.describe(&@shared_block)
              kls.children.first.metadata[:shared_group_name] = name
            end
          end

          shared_const = Object.const_set(name, mod)
          add_shared_example_group source, shared_const, block
        end

        def shared_example_groups_for(*sources)
          Collection.new(sources, shared_example_groups)
        end

        def shared_example_groups
          @shared_example_groups ||= Hash.new { |hash,key| hash[key] = Hash.new }
        end

        def clear
          shared_example_groups.clear
        end

      private

        def add_shared_example_group(source, key, block)
          shared_example_groups[source][key] = block
        end

        def key?(candidate)
          [String, Symbol, Module].any? { |cls| cls === candidate }
        end

        def raise_name_error
          raise NameError, "The first argument (#{name}) to share_as must be a legal name for a constant not already in use."
        end

        def warn_if_key_taken(source, key, new_block)
          return unless existing_block = example_block_for(source, key)

          Kernel.warn <<-WARNING.gsub(/^ +\|/, '')
            |WARNING: Shared example group '#{key}' has been previously defined at:
            |  #{formatted_location existing_block}
            |...and you are now defining it at:
            |  #{formatted_location new_block}
            |The new definition will overwrite the original one.
          WARNING
        end

        def formatted_location(block)
          block.source_location.join ":"
        end

        def example_block_for(source, key)
          shared_example_groups[source][key]
        end

        def ensure_block_has_source_location(block, caller_line)
          return if block.respond_to?(:source_location)

          block.extend Module.new {
            define_method :source_location do
              caller_line.split(':')
            end
          }
        end
      end
    end
  end
end

extend RSpec::Core::SharedExampleGroup::TopLevelDSL
Module.send(:include, RSpec::Core::SharedExampleGroup::TopLevelDSL)