1require 'mspec/expectations/expectations'
2require 'mspec/runner/actions/timer'
3require 'mspec/runner/actions/tally'
4require 'mspec/runner/actions/leakchecker' if ENV['CHECK_LEAKS']
5
6class DottedFormatter
7  attr_reader :exceptions, :timer, :tally
8
9  def initialize(out=nil)
10    @exception = @failure = false
11    @exceptions = []
12    @count = 0 # For subclasses
13    if out.nil?
14      @out = $stdout
15    else
16      @out = File.open out, "w"
17    end
18
19    @current_state = nil
20  end
21
22  # Creates the +TimerAction+ and +TallyAction+ instances and
23  # registers them. Registers +self+ for the +:exception+,
24  # +:before+, +:after+, and +:finish+ actions.
25  def register
26    (@timer = TimerAction.new).register
27    (@tally = TallyAction.new).register
28    LeakCheckerAction.new.register if ENV['CHECK_LEAKS']
29    @counter = @tally.counter
30
31    MSpec.register :exception, self
32    MSpec.register :before,    self
33    MSpec.register :after,     self
34    MSpec.register :finish,    self
35    MSpec.register :abort,     self
36  end
37
38  def abort
39    if @current_state
40      puts "\naborting example: #{@current_state.description}"
41    end
42  end
43
44  # Returns true if any exception is raised while running
45  # an example. This flag is reset before each example
46  # is evaluated.
47  def exception?
48    @exception
49  end
50
51  # Returns true if all exceptions during the evaluation
52  # of an example are failures rather than errors. See
53  # <tt>ExceptionState#failure</tt>. This flag is reset
54  # before each example is evaluated.
55  def failure?
56    @failure
57  end
58
59  # Callback for the MSpec :before event. Resets the
60  # +#exception?+ and +#failure+ flags.
61  def before(state=nil)
62    @current_state = state
63    @failure = @exception = false
64  end
65
66  # Callback for the MSpec :exception event. Stores the
67  # +ExceptionState+ object to generate the list of backtraces
68  # after all the specs are run. Also updates the internal
69  # +#exception?+ and +#failure?+ flags.
70  def exception(exception)
71    @count += 1
72    @failure = @exception ? @failure && exception.failure? : exception.failure?
73    @exception = true
74    @exceptions << exception
75  end
76
77  # Callback for the MSpec :after event. Prints an indicator
78  # for the result of evaluating this example as follows:
79  #   . = No failure or error
80  #   F = An SpecExpectationNotMetError was raised
81  #   E = Any exception other than SpecExpectationNotMetError
82  def after(state = nil)
83    @current_state = nil
84
85    unless exception?
86      print "."
87    else
88      print failure? ? "F" : "E"
89    end
90  end
91
92  # Callback for the MSpec :finish event. Prints a description
93  # and backtrace for every exception that occurred while
94  # evaluating the examples.
95  def finish
96    print "\n"
97    count = 0
98    @exceptions.each do |exc|
99      count += 1
100      print_exception(exc, count)
101    end
102    print "\n#{@timer.format}\n\n#{@tally.format}\n"
103  end
104
105  def print_exception(exc, count)
106    outcome = exc.failure? ? "FAILED" : "ERROR"
107    print "\n#{count})\n#{exc.description} #{outcome}\n"
108    print exc.message, "\n"
109    print exc.backtrace, "\n"
110  end
111
112  # A convenience method to allow printing to different outputs.
113  def print(*args)
114    @out.print(*args)
115    @out.flush
116  end
117end
118