summaryrefslogtreecommitdiff
path: root/spec/ruby/shared/sizedqueue/enque.rb
blob: 2f2551767571dfcc4dc1ee8119c464726948c0cf (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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
describe :sizedqueue_enq, shared: true do
  it "blocks if queued elements exceed size" do
    q = @object.call(1)

    q.size.should == 0
    q.send(@method, :first_element)
    q.size.should == 1

    blocked_thread = Thread.new { q.send(@method, :second_element) }
    sleep 0.01 until blocked_thread.stop?

    q.size.should == 1
    q.pop.should == :first_element

    blocked_thread.join
    q.size.should == 1
    q.pop.should == :second_element
    q.size.should == 0
  end

  it "raises a ThreadError if queued elements exceed size when not blocking" do
    q = @object.call(2)

    non_blocking = true
    add_to_queue = -> { q.send(@method, Object.new, non_blocking) }

    q.size.should == 0
    add_to_queue.call
    q.size.should == 1
    add_to_queue.call
    q.size.should == 2
    add_to_queue.should raise_error(ThreadError)
  end

  it "interrupts enqueuing threads with ClosedQueueError when the queue is closed" do
    q = @object.call(1)
    q << 1

    t = Thread.new {
      -> { q.send(@method, 2) }.should raise_error(ClosedQueueError, "queue closed")
    }

    Thread.pass until q.num_waiting == 1

    q.close

    t.join
    q.pop.should == 1
  end

  describe "with a timeout" do
    ruby_version_is "3.2" do
      it "returns self if the item was pushed in time" do
        q = @object.call(1)
        q << 1

        t = Thread.new {
          q.send(@method, 2, timeout: TIME_TOLERANCE).should == q
        }
        Thread.pass until t.status == "sleep" && q.num_waiting == 1
        q.pop
        t.join
      end

      it "does nothing if the timeout is nil" do
        q = @object.call(1)
        q << 1
        t = Thread.new {
          q.send(@method, 2, timeout: nil).should == q
        }
        t.join(0.2).should == nil
        q.pop
        t.join
      end

      it "returns nil if no space is available and timeout is 0" do
        q = @object.call(1)
        q.send(@method, 1, timeout: 0).should == q
        q.send(@method, 2, timeout: 0).should == nil
      end

      it "returns nil if no space is available in time" do
        q = @object.call(1)
        q << 1
        Thread.new {
          q.send(@method, 2, timeout: 0.001).should == nil
        }.join
      end

      it "raise TypeError if timeout is not a valid numeric" do
        q = @object.call(1)
        -> {
          q.send(@method, 2, timeout: "1")
        }.should raise_error(TypeError, "no implicit conversion to float from string")

        -> {
          q.send(@method, 2, timeout: false)
        }.should raise_error(TypeError, "no implicit conversion to float from false")
      end

      it "raise ArgumentError if non_block = true is passed too" do
        q = @object.call(1)
        -> {
          q.send(@method, 2, true, timeout: 1)
        }.should raise_error(ArgumentError, "can't set a timeout if non_block is enabled")
      end

      it "raise ClosedQueueError when closed before enqueued" do
        q = @object.call(1)
        q.close
        -> { q.send(@method, 2, timeout: 1) }.should raise_error(ClosedQueueError, "queue closed")
      end

      it "interrupts enqueuing threads with ClosedQueueError when the queue is closed" do
        q = @object.call(1)
        q << 1

        t = Thread.new {
          -> { q.send(@method, 1, timeout: TIME_TOLERANCE) }.should raise_error(ClosedQueueError, "queue closed")
        }

        Thread.pass until q.num_waiting == 1

        q.close

        t.join
        q.pop.should == 1
      end
    end
  end
end