mqtt 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/GPL +340 -0
- data/NEWS +19 -11
- data/README +41 -32
- data/lib/mqtt.rb +14 -4
- data/lib/mqtt/client.rb +223 -222
- data/lib/mqtt/packet.rb +330 -150
- data/lib/mqtt/proxy.rb +96 -108
- data/lib/mqtt/version.rb +3 -0
- data/spec/mqtt_client_spec.rb +303 -0
- data/spec/mqtt_packet_spec.rb +1230 -0
- data/spec/mqtt_proxy_spec.rb +8 -0
- data/spec/mqtt_version_spec.rb +23 -0
- metadata +136 -25
- data/Rakefile +0 -112
- data/examples/livetext.rb +0 -14
- data/examples/readonly_proxy.rb +0 -25
- data/examples/simple_get.rb +0 -14
- data/examples/simple_publish.rb +0 -12
data/lib/mqtt/proxy.rb
CHANGED
@@ -1,121 +1,109 @@
|
|
1
|
-
|
1
|
+
# Class for implementing a proxy to filter/mangle MQTT packets.
|
2
|
+
class MQTT::Proxy
|
3
|
+
attr_reader :local_host
|
4
|
+
attr_reader :local_port
|
5
|
+
attr_reader :broker_host
|
6
|
+
attr_reader :broker_port
|
7
|
+
attr_reader :listen_queue
|
8
|
+
attr_reader :select_timeout
|
9
|
+
attr_reader :logger
|
2
10
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
11
|
+
# Create a new MQTT Proxy instance.
|
12
|
+
#
|
13
|
+
# Possible argument keys:
|
14
|
+
#
|
15
|
+
# :local_host Address to bind listening socket to.
|
16
|
+
# :local_port Port to bind listening socket to.
|
17
|
+
# :broker_host Address of upstream broker to send packets upstream to.
|
18
|
+
# :broker_port Port of upstream broker to send packets upstream to.
|
19
|
+
# :select_timeout Time in seconds before disconnecting a connection.
|
20
|
+
# :logger Ruby Logger object to send informational messages to.
|
21
|
+
#
|
22
|
+
# NOTE: be careful not to connect to yourself!
|
23
|
+
def initialize(args={})
|
24
|
+
@local_host = args[:local_host] || '0.0.0.0'
|
25
|
+
@local_port = args[:local_port] || MQTT::DEFAULT_PORT
|
26
|
+
@broker_host = args[:broker_host] || MQTT::DEFAULT_HOST
|
27
|
+
@broker_port = args[:broker_port] || 18830
|
28
|
+
@select_timeout = args[:select_timeout] || 60
|
8
29
|
|
30
|
+
# Setup a logger
|
31
|
+
@logger = args[:logger]
|
32
|
+
if @logger.nil?
|
33
|
+
@logger = Logger.new(STDOUT)
|
34
|
+
@logger.level = Logger::INFO
|
35
|
+
end
|
9
36
|
|
10
|
-
|
37
|
+
# Default is not to have any filters
|
38
|
+
@client_filter = nil
|
39
|
+
@broker_filter = nil
|
11
40
|
|
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
|
-
@broker_port = args[:broker_port] || 18830
|
39
|
-
@select_timeout = args[:select_timeout] || 60
|
40
|
-
|
41
|
-
# Setup a logger
|
42
|
-
@logger = args[:logger]
|
43
|
-
if @logger.nil?
|
44
|
-
@logger = Logger.new(STDOUT)
|
45
|
-
@logger.level = Logger::INFO
|
46
|
-
end
|
47
|
-
|
48
|
-
# Default is not to have any filters
|
49
|
-
@client_filter = nil
|
50
|
-
@broker_filter = nil
|
51
|
-
|
52
|
-
# Create TCP server socket
|
53
|
-
@server = TCPServer.open(@local_host,@local_port)
|
54
|
-
@logger.info "MQTT::Proxy listening on #{@local_host}:#{@local_port}"
|
55
|
-
end
|
56
|
-
|
57
|
-
# Set a filter Proc for packets coming from the client (to the broker).
|
58
|
-
def client_filter=(proc)
|
59
|
-
@client_filter = proc
|
60
|
-
end
|
61
|
-
|
62
|
-
# Set a filter Proc for packets coming from the broker (to the client).
|
63
|
-
def broker_filter=(proc)
|
64
|
-
@broker_filter = proc
|
65
|
-
end
|
66
|
-
|
67
|
-
# Start accepting connections and processing packets.
|
68
|
-
def run
|
69
|
-
loop do
|
70
|
-
# Wait for a client to connect and then create a thread for it
|
71
|
-
Thread.new(@server.accept) do |client_socket|
|
72
|
-
logger.info "Accepted client: #{client_socket.peeraddr.join(':')}"
|
73
|
-
broker_socket = TCPSocket.new(@broker_host,@broker_port)
|
74
|
-
begin
|
75
|
-
process_packets(client_socket,broker_socket)
|
76
|
-
rescue Exception => exp
|
77
|
-
logger.error exp.to_s
|
78
|
-
end
|
79
|
-
logger.info "Disconnected: #{client_socket.peeraddr.join(':')}"
|
80
|
-
broker_socket.close
|
81
|
-
client_socket.close
|
41
|
+
# Create TCP server socket
|
42
|
+
@server = TCPServer.open(@local_host,@local_port)
|
43
|
+
@logger.info "MQTT::Proxy listening on #{@local_host}:#{@local_port}"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Set a filter Proc for packets coming from the client (to the broker).
|
47
|
+
def client_filter=(proc)
|
48
|
+
@client_filter = proc
|
49
|
+
end
|
50
|
+
|
51
|
+
# Set a filter Proc for packets coming from the broker (to the client).
|
52
|
+
def broker_filter=(proc)
|
53
|
+
@broker_filter = proc
|
54
|
+
end
|
55
|
+
|
56
|
+
# Start accepting connections and processing packets.
|
57
|
+
def run
|
58
|
+
loop do
|
59
|
+
# Wait for a client to connect and then create a thread for it
|
60
|
+
Thread.new(@server.accept) do |client_socket|
|
61
|
+
logger.info "Accepted client: #{client_socket.peeraddr.join(':')}"
|
62
|
+
broker_socket = TCPSocket.new(@broker_host,@broker_port)
|
63
|
+
begin
|
64
|
+
process_packets(client_socket,broker_socket)
|
65
|
+
rescue Exception => exp
|
66
|
+
logger.error exp.to_s
|
82
67
|
end
|
68
|
+
logger.info "Disconnected: #{client_socket.peeraddr.join(':')}"
|
69
|
+
broker_socket.close
|
70
|
+
client_socket.close
|
83
71
|
end
|
84
72
|
end
|
73
|
+
end
|
85
74
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
end
|
105
|
-
elsif selected[0].include?(broker_socket)
|
106
|
-
packet = MQTT::Packet.read(broker_socket)
|
107
|
-
logger.debug "broker -> <#{packet.type}>"
|
108
|
-
packet = @broker_filter.call(packet) unless @broker_filter.nil?
|
109
|
-
unless packet.nil?
|
110
|
-
client_socket.write(packet)
|
111
|
-
logger.debug "<#{packet.type}> -> client"
|
112
|
-
end
|
113
|
-
else
|
114
|
-
logger.error "Problem with select: socket is neither broker or client"
|
75
|
+
private
|
76
|
+
|
77
|
+
def process_packets(client_socket,broker_socket)
|
78
|
+
loop do
|
79
|
+
# Wait for some data on either socket
|
80
|
+
selected = IO.select([client_socket,broker_socket], nil, nil, @select_timeout)
|
81
|
+
if selected.nil?
|
82
|
+
# Timeout
|
83
|
+
raise "Timeout in select"
|
84
|
+
else
|
85
|
+
# Iterate through each of the sockets with data to read
|
86
|
+
if selected[0].include?(client_socket)
|
87
|
+
packet = MQTT::Packet.read(client_socket)
|
88
|
+
logger.debug "client -> <#{packet.type}>"
|
89
|
+
packet = @client_filter.call(packet) unless @client_filter.nil?
|
90
|
+
unless packet.nil?
|
91
|
+
broker_socket.write(packet)
|
92
|
+
logger.debug "<#{packet.type}> -> broker"
|
115
93
|
end
|
94
|
+
elsif selected[0].include?(broker_socket)
|
95
|
+
packet = MQTT::Packet.read(broker_socket)
|
96
|
+
logger.debug "broker -> <#{packet.type}>"
|
97
|
+
packet = @broker_filter.call(packet) unless @broker_filter.nil?
|
98
|
+
unless packet.nil?
|
99
|
+
client_socket.write(packet)
|
100
|
+
logger.debug "<#{packet.type}> -> client"
|
101
|
+
end
|
102
|
+
else
|
103
|
+
logger.error "Problem with select: socket is neither broker or client"
|
116
104
|
end
|
117
105
|
end
|
118
106
|
end
|
119
|
-
|
120
107
|
end
|
121
|
-
|
108
|
+
|
109
|
+
end
|
data/lib/mqtt/version.rb
ADDED
@@ -0,0 +1,303 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'mqtt'
|
5
|
+
|
6
|
+
describe MQTT::Client do
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
@client = MQTT::Client.new
|
10
|
+
@socket = StringIO.new
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "when calling the 'connect' method" do
|
14
|
+
before(:each) do
|
15
|
+
TCPSocket.stubs(:new).returns(@socket)
|
16
|
+
Thread.stubs(:new)
|
17
|
+
@client.stubs(:receive_connack)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should create a TCP Socket if not connected" do
|
21
|
+
TCPSocket.expects(:new).once.returns(@socket)
|
22
|
+
@client.connect('myclient')
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should not create a new TCP Socket if connected" do
|
26
|
+
@client.stubs(:connected?).returns(true)
|
27
|
+
TCPSocket.expects(:new).never
|
28
|
+
@client.connect('myclient')
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should start the reader thread if not connected" do
|
32
|
+
Thread.expects(:new).once
|
33
|
+
@client.connect('myclient')
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should write a valid CONNECT packet to the socket if not connected" do
|
37
|
+
@client.connect('myclient')
|
38
|
+
@socket.string.should == "\020\026\x00\x06MQIsdp\x03\x02\x00\x0a\x00\x08myclient"
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should try and read an acknowledgement packet to the socket if not connected" do
|
42
|
+
@client.expects(:receive_connack).once
|
43
|
+
@client.connect('myclient')
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should disconnect after connecting, if a block is given" do
|
47
|
+
@client.expects(:disconnect).once
|
48
|
+
@client.connect('myclient') { nil }
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should not disconnect after connecting, if no block is given" do
|
52
|
+
@client.expects(:disconnect).never
|
53
|
+
@client.connect('myclient')
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should include the username and password for an authenticated connection" do
|
57
|
+
@client.username = 'username'
|
58
|
+
@client.password = 'password'
|
59
|
+
@client.connect('myclient')
|
60
|
+
@socket.string.should ==
|
61
|
+
"\x10\x2A"+
|
62
|
+
"\x00\x06MQIsdp"+
|
63
|
+
"\x03\xC2\x00\x0a\x00"+
|
64
|
+
"\x08myclient"+
|
65
|
+
"\x00\x08username"+
|
66
|
+
"\x00\x08password"
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should reset clean session flag to true, if no client id is given" do
|
70
|
+
@client.client_id = nil
|
71
|
+
@client.clean_session = false
|
72
|
+
@client.connect
|
73
|
+
@client.clean_session.should be_true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "when calling the 'receive_connack' method" do
|
78
|
+
before(:each) do
|
79
|
+
@client.instance_variable_set(:@socket, @socket)
|
80
|
+
IO.stubs(:select).returns([[@socket], [], []])
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should not throw an exception for a successful CONNACK packet" do
|
84
|
+
@socket.write("\x20\x02\x00\x00")
|
85
|
+
@socket.rewind
|
86
|
+
lambda { @client.send(:receive_connack) }.should_not raise_error
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should throw an exception if the packet type isn't CONNACK" do
|
90
|
+
@socket.write("\xD0\x00")
|
91
|
+
@socket.rewind
|
92
|
+
lambda { @client.send(:receive_connack) }.should raise_error(MQTT::ProtocolException)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should throw an exception if the CONNACK packet return code is 'unacceptable protocol version'" do
|
96
|
+
@socket.write("\x20\x02\x00\x01")
|
97
|
+
@socket.rewind
|
98
|
+
lambda { @client.send(:receive_connack) }.should raise_error(MQTT::ProtocolException, /unacceptable protocol version/i)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should throw an exception if the CONNACK packet return code is 'client identifier rejected'" do
|
102
|
+
@socket.write("\x20\x02\x00\x02")
|
103
|
+
@socket.rewind
|
104
|
+
lambda { @client.send(:receive_connack) }.should raise_error(MQTT::ProtocolException, /client identifier rejected/i)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should throw an exception if the CONNACK packet return code is 'broker unavailable'" do
|
108
|
+
@socket.write("\x20\x02\x00\x03")
|
109
|
+
@socket.rewind
|
110
|
+
lambda { @client.send(:receive_connack) }.should raise_error(MQTT::ProtocolException, /broker unavailable/i)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should throw an exception if the CONNACK packet return code is an unknown" do
|
114
|
+
@socket.write("\x20\x02\x00\xAA")
|
115
|
+
@socket.rewind
|
116
|
+
lambda { @client.send(:receive_connack) }.should raise_error(MQTT::ProtocolException, /connection refused/i)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "when calling the 'disconnect' method" do
|
121
|
+
before(:each) do
|
122
|
+
@client.instance_variable_set(:@socket, @socket)
|
123
|
+
@client.instance_variable_set(:@read_thread, stub_everything('Read Thread'))
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should not do anything if the socket is already disconnected" do
|
127
|
+
@client.stubs(:connected?).returns(false)
|
128
|
+
@client.disconnect(true)
|
129
|
+
@socket.string.should == ""
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should write a valid DISCONNECT packet to the socket if connected and the send_msg=true an" do
|
133
|
+
@client.stubs(:connected?).returns(true)
|
134
|
+
@client.disconnect(true)
|
135
|
+
@socket.string.should == "\xE0\x00"
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should not write anything to the socket if the send_msg=false" do
|
139
|
+
@client.stubs(:connected?).returns(true)
|
140
|
+
@client.disconnect(false)
|
141
|
+
@socket.string.should be_empty
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should call the close method on the socket" do
|
145
|
+
@socket.expects(:close)
|
146
|
+
@client.disconnect
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe "when calling the 'ping' method" do
|
151
|
+
before(:each) do
|
152
|
+
@client.instance_variable_set(:@socket, @socket)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should write a valid PINGREQ packet to the socket" do
|
156
|
+
@client.ping
|
157
|
+
@socket.string.should == "\xC0\x00"
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should update the time a ping was last sent" do
|
161
|
+
@client.instance_variable_set(:@last_pingreq, 0)
|
162
|
+
@client.ping
|
163
|
+
@client.instance_variable_get(:@last_pingreq).should_not == 0
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe "when calling the 'publish' method" do
|
168
|
+
before(:each) do
|
169
|
+
@client.instance_variable_set(:@socket, @socket)
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should write a valid PUBLISH packet to the socket without the retain flag" do
|
173
|
+
@client.publish('topic','payload', false, 0)
|
174
|
+
@socket.string.should == "\x30\x0e\x00\x05topicpayload"
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should write a valid PUBLISH packet to the socket with the retain flag set" do
|
178
|
+
@client.publish('topic','payload', true, 0)
|
179
|
+
@socket.string.should == "\x31\x0e\x00\x05topicpayload"
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should write a valid PUBLISH packet to the socket with the QOS set to 1" do
|
183
|
+
@client.publish('topic','payload', false, 1)
|
184
|
+
@socket.string.should == "\x32\x10\x00\x05topic\x00\x01payload"
|
185
|
+
end
|
186
|
+
|
187
|
+
it "should write a valid PUBLISH packet to the socket with the QOS set to 2" do
|
188
|
+
@client.publish('topic','payload', false, 2)
|
189
|
+
@socket.string.should == "\x34\x10\x00\x05topic\x00\x01payload"
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
describe "when calling the 'subscribe' method" do
|
194
|
+
before(:each) do
|
195
|
+
@client.instance_variable_set(:@socket, @socket)
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should write a valid SUBSCRIBE packet to the socket if given a single topic String" do
|
199
|
+
@client.subscribe('a/b')
|
200
|
+
@socket.string.should == "\x82\x08\x00\x01\x00\x03a/b\x00"
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should write a valid SUBSCRIBE packet to the socket if given a two topic Strings in an Array" do
|
204
|
+
@client.subscribe('a/b','c/d')
|
205
|
+
@socket.string.should == "\x82\x0e\x00\x01\x00\x03a/b\x00\x00\x03c/d\x00"
|
206
|
+
end
|
207
|
+
|
208
|
+
it "should write a valid SUBSCRIBE packet to the socket if given a two topic Strings with QoS in an Array" do
|
209
|
+
@client.subscribe(['a/b',0],['c/d',1])
|
210
|
+
@socket.string.should == "\x82\x0e\x00\x01\x00\x03a/b\x00\x00\x03c/d\x01"
|
211
|
+
end
|
212
|
+
|
213
|
+
it "should write a valid SUBSCRIBE packet to the socket if given a two topic Strings with QoS in a Hash" do
|
214
|
+
@client.subscribe('a/b' => 0,'c/d' => 1)
|
215
|
+
@socket.string.should == "\x82\x0e\x00\x01\x00\x03a/b\x00\x00\x03c/d\x01"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
describe "when calling the 'get' method" do
|
220
|
+
before(:each) do
|
221
|
+
@client.instance_variable_set(:@socket, @socket)
|
222
|
+
end
|
223
|
+
|
224
|
+
def inject_packet(opts={})
|
225
|
+
packet = MQTT::Packet::Publish.new(opts)
|
226
|
+
@client.instance_variable_get('@read_queue').push(packet)
|
227
|
+
end
|
228
|
+
|
229
|
+
it "should successfull receive a valid PUBLISH packet with a QoS 0" do
|
230
|
+
inject_packet(:topic => 'topic0', :payload => 'payload0', :qos => 0)
|
231
|
+
topic,payload = @client.get
|
232
|
+
topic.should == 'topic0'
|
233
|
+
payload.should == 'payload0'
|
234
|
+
end
|
235
|
+
|
236
|
+
it "should successfull receive a valid PUBLISH packet with a QoS 1" do
|
237
|
+
inject_packet(:topic => 'topic1', :payload => 'payload1', :qos => 1)
|
238
|
+
topic,payload = @client.get
|
239
|
+
topic.should == 'topic1'
|
240
|
+
payload.should == 'payload1'
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
describe "when calling the 'unsubscribe' method" do
|
245
|
+
before(:each) do
|
246
|
+
@client.instance_variable_set(:@socket, @socket)
|
247
|
+
end
|
248
|
+
|
249
|
+
it "should write a valid UNSUBSCRIBE packet to the socket if given a single topic String" do
|
250
|
+
@client.unsubscribe('a/b')
|
251
|
+
@socket.string.should == "\xa2\x07\x00\x01\x00\x03a/b"
|
252
|
+
end
|
253
|
+
|
254
|
+
it "should write a valid UNSUBSCRIBE packet to the socket if given a two topic Strings" do
|
255
|
+
@client.unsubscribe('a/b','c/d')
|
256
|
+
@socket.string.should == "\xa2\x0c\x00\x01\x00\x03a/b\x00\x03c/d"
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
describe "when calling the 'receive_packet' method" do
|
261
|
+
before(:each) do
|
262
|
+
@client.instance_variable_set(:@socket, @socket)
|
263
|
+
IO.stubs(:select).returns([[@socket], [], []])
|
264
|
+
@read_queue = @client.instance_variable_get(:@read_queue)
|
265
|
+
@parent_thread = Thread.current[:parent] = stub_everything('Parent Thread')
|
266
|
+
end
|
267
|
+
|
268
|
+
it "should put PUBLISH messages on to the read queue" do
|
269
|
+
@socket.write("\x30\x0e\x00\x05topicpayload")
|
270
|
+
@socket.rewind
|
271
|
+
@client.send(:receive_packet)
|
272
|
+
@read_queue.size.should == 1
|
273
|
+
end
|
274
|
+
|
275
|
+
it "should not put other messages on to the read queue" do
|
276
|
+
@socket.write("\x20\x02\x00\x00")
|
277
|
+
@socket.rewind
|
278
|
+
@client.send(:receive_packet)
|
279
|
+
@read_queue.size.should == 0
|
280
|
+
end
|
281
|
+
|
282
|
+
it "should send a ping packet if one is due" do
|
283
|
+
IO.expects(:select).returns(nil)
|
284
|
+
@client.instance_variable_set(:@last_pingreq, Time.at(0))
|
285
|
+
@client.expects(:ping).once
|
286
|
+
@client.send(:receive_packet)
|
287
|
+
end
|
288
|
+
|
289
|
+
it "should close the socket if there is an exception" do
|
290
|
+
@socket.expects(:close).once
|
291
|
+
MQTT::Packet.stubs(:read).raises(MQTT::Exception)
|
292
|
+
@client.send(:receive_packet)
|
293
|
+
end
|
294
|
+
|
295
|
+
it "should pass exceptions up to parent thread" do
|
296
|
+
@parent_thread.expects(:raise).once
|
297
|
+
MQTT::Packet.stubs(:read).raises(MQTT::Exception)
|
298
|
+
@client.send(:receive_packet)
|
299
|
+
end
|
300
|
+
|
301
|
+
end
|
302
|
+
|
303
|
+
end
|