summaryrefslogtreecommitdiff
path: root/spec/mspec/lib/mspec/matchers/output_to_fd.rb
blob: f4d7b4ea1f1bba3d5b339d00e0f4bec8d9674932 (plain)
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
require 'mspec/helpers/tmp'

# Lower-level output speccing mechanism for a single
# output stream. Unlike OutputMatcher which provides
# methods to capture the output, we actually replace
# the FD itself so that there is no reliance on a
# certain method being used.
class OutputToFDMatcher
  def initialize(expected, to)
    @to, @expected = to, expected

    case @to
    when STDOUT
      @to_name = "STDOUT"
    when STDERR
      @to_name = "STDERR"
    when IO
      @to_name = @to.object_id.to_s
    else
      raise ArgumentError, "#{@to.inspect} is not a supported output target"
    end
  end

  def with_tmp
    path = tmp("mspec_output_to_#{$$}_#{Time.now.to_i}")
    File.open(path, 'w+') { |io|
      yield(io)
    }
  ensure
    File.delete path if path
  end

  def matches?(block)
    old_to = @to.dup
    with_tmp do |out|
      # Replacing with a file handle so that Readline etc. work
      @to.reopen out
      begin
        block.call
      ensure
        @to.reopen old_to
        old_to.close
      end

      out.rewind
      @actual = out.read

      case @expected
      when Regexp
        !(@actual =~ @expected).nil?
      else
        @actual == @expected
      end
    end
  end

  def failure_message()
    ["Expected (#{@to_name}): #{@expected.inspect}\n",
     "#{'but got'.rjust(@to_name.length + 10)}: #{@actual.inspect}\nBacktrace"]
  end

  def negative_failure_message()
    ["Expected output (#{@to_name}) to NOT be:\n", @actual.inspect]
  end
end

module MSpecMatchers
  private def output_to_fd(what, where = STDOUT)
    OutputToFDMatcher.new what, where
  end
end