summaryrefslogtreecommitdiff
path: root/spec/spec_helpers/repl_tester.rb
blob: 45045af60f279767df98f8f7b71e4bdc8a9019ee (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
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
# This is for super-high-level integration testing.

require 'thread'
require 'delegate'

class ReplTester
  class Input
    def initialize(tester_mailbox)
      @tester_mailbox = tester_mailbox
    end

    def readline(prompt)
      @tester_mailbox.push prompt
      mailbox.pop
    end

    def mailbox
      Thread.current[:mailbox]
    end
  end

  class Output < SimpleDelegator
    def clear
      __setobj__(StringIO.new)
    end
  end

  def self.start(options = {}, &block)
    Thread.current[:mailbox] = Queue.new
    instance = nil
    input    = Input.new(Thread.current[:mailbox])
    output   = Output.new(StringIO.new)

    redirect_pry_io input, output do
      instance = new(options)
      instance.instance_eval(&block)
      instance.ensure_exit
    end
  ensure
    if instance && instance.thread && instance.thread.alive?
      instance.thread.kill
    end
  end

  attr_accessor :thread, :mailbox, :last_prompt

  def initialize(options = {})
    @pry     = Pry.new(options)
    @repl    = Pry::REPL.new(@pry)
    @mailbox = Thread.current[:mailbox]

    @thread  = Thread.new do
      begin
        Thread.current[:mailbox] = Queue.new
        @repl.start
      ensure
        Thread.current[:session_ended] = true
        mailbox.push nil
      end
    end

    wait # wait until the instance reaches its first readline
  end

  # Accept a line of input, as if entered by a user.
  def input(input)
    reset_output
    repl_mailbox.push input
    wait
    @pry.output.string
  end

  # Assert that the current prompt matches the given string or regex.
  def prompt(match)
    match.should === last_prompt
  end

  # Assert that the most recent output (since the last time input was called)
  # matches the given string or regex.
  def output(match)
    match.should === @pry.output.string.chomp
  end

  # Assert that the Pry session ended naturally after the last input.
  def assert_exited
    @should_exit_naturally = true
  end

  # @private
  def ensure_exit
    if @should_exit_naturally
      @thread[:session_ended].should.be_true
    else
      input "exit-all"
      raise "REPL didn't die" unless @thread[:session_ended]
    end
  end

  private

  def reset_output
    @pry.output.clear
  end

  def repl_mailbox
    @thread[:mailbox]
  end

  def wait
    @last_prompt = mailbox.pop
  end
end