public
Description: Behaviour Driven Development framework for Ruby
Homepage: http://rspec.info
Clone URL: git://github.com/dchelimsky/rspec.git
Click here to lend your support to: rspec and make a donation at www.pledgie.com !
btakita (author)
Thu May 22 01:02:02 -0700 2008
commit  294acb030f811fd546735aa819cdcf89d142b635
tree    547c9a522da11fa7c09c0693aafe245bd18808ca
parent  dff59d633d71eeb621bcd6b9f5d1238f78c62647
rspec / lib / spec / runner / options.rb
100644 310 lines (277 sloc) 10.491 kb
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
module Spec
  module Runner
    class Options
      FILE_SORTERS = {
        'mtime' => lambda {|file_a, file_b| File.mtime(file_b) <=> File.mtime(file_a)}
      }
 
      EXAMPLE_FORMATTERS = { # Load these lazily for better speed
               'specdoc' => ['spec/runner/formatter/specdoc_formatter', 'Formatter::SpecdocFormatter'],
                     's' => ['spec/runner/formatter/specdoc_formatter', 'Formatter::SpecdocFormatter'],
              'indented' => ['spec/runner/formatter/indented_text_formatter', 'Formatter::IndentedTextFormatter'],
                     'i' => ['spec/runner/formatter/indented_text_formatter', 'Formatter::IndentedTextFormatter'],
                  'html' => ['spec/runner/formatter/html_formatter', 'Formatter::HtmlFormatter'],
                     'h' => ['spec/runner/formatter/html_formatter', 'Formatter::HtmlFormatter'],
              'progress' => ['spec/runner/formatter/progress_bar_formatter', 'Formatter::ProgressBarFormatter'],
                     'p' => ['spec/runner/formatter/progress_bar_formatter', 'Formatter::ProgressBarFormatter'],
      'failing_examples' => ['spec/runner/formatter/failing_examples_formatter', 'Formatter::FailingExamplesFormatter'],
                     'e' => ['spec/runner/formatter/failing_examples_formatter', 'Formatter::FailingExamplesFormatter'],
'failing_example_groups' => ['spec/runner/formatter/failing_example_groups_formatter', 'Formatter::FailingExampleGroupsFormatter'],
                     'g' => ['spec/runner/formatter/failing_example_groups_formatter', 'Formatter::FailingExampleGroupsFormatter'],
               'profile' => ['spec/runner/formatter/profile_formatter', 'Formatter::ProfileFormatter'],
                     'o' => ['spec/runner/formatter/profile_formatter', 'Formatter::ProfileFormatter'],
              'textmate' => ['spec/runner/formatter/text_mate_formatter', 'Formatter::TextMateFormatter']
      }
 
      STORY_FORMATTERS = {
        'plain' => ['spec/runner/formatter/story/plain_text_formatter', 'Formatter::Story::PlainTextFormatter'],
            'p' => ['spec/runner/formatter/story/plain_text_formatter', 'Formatter::Story::PlainTextFormatter'],
         'html' => ['spec/runner/formatter/story/html_formatter', 'Formatter::Story::HtmlFormatter'],
            'h' => ['spec/runner/formatter/story/html_formatter', 'Formatter::Story::HtmlFormatter']
      }
 
      attr_accessor(
        :filename_pattern,
        :backtrace_tweaker,
        :context_lines,
        :diff_format,
        :dry_run,
        :profile,
        :examples,
        :heckle_runner,
        :line_number,
        :loadby,
        :reporter,
        :reverse,
        :timeout,
        :verbose,
        :user_input_for_runner,
        :error_stream,
        :output_stream,
        :before_suite_parts,
        :after_suite_parts,
        # TODO: BT - Figure out a better name
        :argv
      )
      attr_reader :colour, :differ_class, :files, :example_groups
 
      def initialize(error_stream, output_stream)
        @error_stream = error_stream
        @output_stream = output_stream
        @filename_pattern = "**/*_spec.rb"
        @backtrace_tweaker = QuietBacktraceTweaker.new
        @examples = []
        @colour = false
        @profile = false
        @dry_run = false
        @reporter = Reporter.new(self)
        @context_lines = 3
        @diff_format = :unified
        @files = []
        @example_groups = []
        @result = nil
        @examples_run = false
        @examples_should_be_run = nil
        @user_input_for_runner = nil
        @before_suite_parts = []
        @after_suite_parts = []
      end
 
      def add_example_group(example_group)
        @example_groups << example_group
      end
 
      def remove_example_group(example_group)
        @example_groups.delete(example_group)
      end
 
      def run_examples
        return true unless examples_should_be_run?
        success = true
        begin
          before_suite_parts.each do |part|
            part.call
          end
          runner = custom_runner || ExampleGroupRunner.new(self)
 
          unless @files_loaded
            runner.load_files(files_to_load)
            @files_loaded = true
          end
 
          if example_groups.empty?
            true
          else
            set_spec_from_line_number if line_number
            success = runner.run
            @examples_run = true
            heckle if heckle_runner
            success
          end
        ensure
          after_suite_parts.each do |part|
            part.call(success)
          end
        end
      end
 
      def examples_run?
        @examples_run
      end
 
      def examples_should_not_be_run
        @examples_should_be_run = false
      end
 
      def colour=(colour)
        @colour = colour
        if @colour && RUBY_PLATFORM =~ /win32/ ;\
          begin ;\
            require 'rubygems' ;\
            require 'Win32/Console/ANSI' ;\
          rescue LoadError ;\
            warn "You must 'gem install win32console' to use colour on Windows" ;\
            @colour = false ;\
          end
        end
      end
 
      def parse_diff(format)
        case format
        when :context, 'context', 'c'
          @diff_format = :context
          default_differ
        when :unified, 'unified', 'u', '', nil
          @diff_format = :unified
          default_differ
        else
          @diff_format = :custom
          self.differ_class = load_class(format, 'differ', '--diff')
        end
      end
 
      def parse_example(example)
        if(File.file?(example))
          @examples = File.open(example).read.split("\n")
        else
          @examples = [example]
        end
      end
 
      def parse_format(format_arg)
        format, where = ClassAndArgumentsParser.parse(format_arg)
        unless where
          raise "When using several --format options only one of them can be without a file" if @out_used
          where = @output_stream
          @out_used = true
        end
        @format_options ||= []
        @format_options << [format, where]
      end
      
      def formatters
        @format_options ||= [['progress', @output_stream]]
        @formatters ||= load_formatters(@format_options, EXAMPLE_FORMATTERS)
      end
 
      def story_formatters
        @format_options ||= [['plain', @output_stream]]
        @formatters ||= load_formatters(@format_options, STORY_FORMATTERS)
      end
      
      def load_formatters(format_options, formatters)
        format_options.map do |format, where|
          formatter_type = if formatters[format]
            require formatters[format][0]
            eval(formatters[format][1], binding, __FILE__, __LINE__)
          else
            load_class(format, 'formatter', '--format')
          end
          formatter_type.new(self, where)
        end
      end
 
      def load_heckle_runner(heckle)
        suffix = [/mswin/, /java/].detect{|p| p =~ RUBY_PLATFORM} ? '_unsupported' : ''
        require "spec/runner/heckle_runner#{suffix}"
        @heckle_runner = HeckleRunner.new(heckle)
      end
 
      def number_of_examples
        total = 0
        @example_groups.each do |example_group|
          total += example_group.number_of_examples
        end
        total
      end
 
      def files_to_load
        result = []
        sorted_files.each do |file|
          if File.directory?(file)
            filename_pattern.split(",").each do |pattern|
              result += Dir[File.expand_path("#{file}/#{pattern.strip}")]
            end
          elsif File.file?(file)
            result << file
          else
            raise "File or directory not found: #{file}"
          end
        end
        result
      end
      
      protected
      def examples_should_be_run?
        return @examples_should_be_run unless @examples_should_be_run.nil?
        @examples_should_be_run = true
      end
      
      def differ_class=(klass)
        return unless klass
        @differ_class = klass
        Spec::Expectations.differ = self.differ_class.new(self)
      end
 
      def load_class(name, kind, option)
        if name =~ /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/
          arg = $2 == "" ? nil : $2
          [$1, arg]
        else
          m = "#{name.inspect} is not a valid class name"
          @error_stream.puts m
          raise m
        end
        begin
          eval(name, binding, __FILE__, __LINE__)
        rescue NameError => e
          @error_stream.puts "Couldn't find #{kind} class #{name}"
          @error_stream.puts "Make sure the --require option is specified *before* #{option}"
          if $_spec_spec ; raise e ; else exit(1) ; end
        end
      end
      
      def custom_runner
        return nil unless custom_runner?
        klass_name, arg = ClassAndArgumentsParser.parse(user_input_for_runner)
        runner_type = load_class(klass_name, 'behaviour runner', '--runner')
        return runner_type.new(self, arg)
      end
 
      def custom_runner?
        return user_input_for_runner ? true : false
      end
      
      def heckle
        heckle_runner = self.heckle_runner
        self.heckle_runner = nil
        heckle_runner.heckle_with
      end
      
      def sorted_files
        return sorter ? files.sort(&sorter) : files
      end
 
      def sorter
        FILE_SORTERS[loadby]
      end
 
      def default_differ
        require 'spec/expectations/differs/default'
        self.differ_class = Spec::Expectations::Differs::Default
      end
 
      def set_spec_from_line_number
        if examples.empty?
          if files.length == 1
            if File.directory?(files[0])
              error_stream.puts "You must specify one file, not a directory when using the --line option"
              exit(1) if stderr?
            else
              example = SpecParser.new.spec_name_for(files[0], line_number)
              @examples = [example]
            end
          else
            error_stream.puts "Only one file can be specified when using the --line option: #{files.inspect}"
            exit(3) if stderr?
          end
        else
          error_stream.puts "You cannot use both --line and --example"
          exit(4) if stderr?
        end
      end
 
      def stderr?
        @error_stream == $stderr
      end
    end
  end
end