rbczmq 1.7.4 → 1.7.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +5 -13
  2. data/.travis.yml +4 -1
  3. data/CHANGELOG.rdoc +13 -0
  4. data/Gemfile +8 -1
  5. data/Gemfile.lock +211 -2
  6. data/README.rdoc +7 -4
  7. data/ext/rbczmq/beacon.c +6 -3
  8. data/ext/rbczmq/context.c +117 -8
  9. data/ext/rbczmq/context.h +5 -0
  10. data/ext/rbczmq/message.c +6 -4
  11. data/ext/rbczmq/poller.c +12 -5
  12. data/ext/rbczmq/rbczmq_ext.c +2 -0
  13. data/ext/rbczmq/rbczmq_ext.h +1 -0
  14. data/ext/rbczmq/socket.c +135 -59
  15. data/ext/rbczmq/socket.h +2 -0
  16. data/ext/zeromq/CMakeLists.txt +3 -1
  17. data/ext/zeromq/NEWS +28 -3
  18. data/ext/zeromq/doc/zmq.txt +3 -3
  19. data/ext/zeromq/doc/zmq_getsockopt.txt +3 -3
  20. data/ext/zeromq/doc/zmq_msg_get.txt +2 -1
  21. data/ext/zeromq/doc/zmq_msg_more.txt +1 -1
  22. data/ext/zeromq/doc/zmq_setsockopt.txt +18 -19
  23. data/ext/zeromq/doc/zmq_socket.txt +4 -0
  24. data/ext/zeromq/include/zmq.h +14 -18
  25. data/ext/zeromq/src/clock.cpp +57 -2
  26. data/ext/zeromq/src/ctx.cpp +11 -5
  27. data/ext/zeromq/src/devpoll.cpp +5 -0
  28. data/ext/zeromq/src/devpoll.hpp +2 -0
  29. data/ext/zeromq/src/epoll.cpp +5 -0
  30. data/ext/zeromq/src/epoll.hpp +2 -0
  31. data/ext/zeromq/src/i_engine.hpp +2 -2
  32. data/ext/zeromq/src/kqueue.cpp +5 -0
  33. data/ext/zeromq/src/kqueue.hpp +2 -0
  34. data/ext/zeromq/src/pgm_receiver.cpp +2 -2
  35. data/ext/zeromq/src/pgm_receiver.hpp +2 -2
  36. data/ext/zeromq/src/pgm_sender.cpp +2 -2
  37. data/ext/zeromq/src/pgm_sender.hpp +2 -2
  38. data/ext/zeromq/src/poll.cpp +5 -0
  39. data/ext/zeromq/src/poll.hpp +2 -0
  40. data/ext/zeromq/src/router.cpp +2 -0
  41. data/ext/zeromq/src/select.cpp +5 -0
  42. data/ext/zeromq/src/select.hpp +2 -0
  43. data/ext/zeromq/src/session_base.cpp +2 -2
  44. data/ext/zeromq/src/signaler.cpp +73 -99
  45. data/ext/zeromq/src/signaler.hpp +2 -2
  46. data/ext/zeromq/src/socket_base.cpp +42 -40
  47. data/ext/zeromq/src/stream_engine.cpp +60 -58
  48. data/ext/zeromq/src/stream_engine.hpp +7 -8
  49. data/ext/zeromq/src/zmq_utils.cpp +6 -5
  50. data/ext/zeromq/tests/Makefile.am +6 -5
  51. data/ext/zeromq/tests/test_conflate.cpp +2 -5
  52. data/ext/zeromq/tests/test_ctx_destroy.cpp +1 -1
  53. data/ext/zeromq/tests/test_ctx_options.cpp +1 -1
  54. data/ext/zeromq/tests/test_immediate.cpp +1 -2
  55. data/ext/zeromq/tests/test_inproc_connect.cpp +1 -1
  56. data/ext/zeromq/tests/test_iov.cpp +1 -1
  57. data/ext/zeromq/tests/test_many_sockets.cpp +90 -0
  58. data/ext/zeromq/tests/test_monitor.cpp +3 -3
  59. data/ext/zeromq/tests/test_req_relaxed.cpp +1 -1
  60. data/ext/zeromq/tests/test_router_raw_empty.cpp +65 -0
  61. data/ext/zeromq/tests/test_spec_req.cpp +1 -1
  62. data/ext/zeromq/tests/test_stream.cpp +6 -7
  63. data/ext/zeromq/tests/test_sub_forward.cpp +1 -1
  64. data/ext/zeromq/tests/test_term_endpoint.cpp +2 -2
  65. data/ext/zeromq/tests/testutil.hpp +18 -1
  66. data/ext/zeromq/tools/Makefile.am +1 -1
  67. data/lib/zmq/socket.rb +1 -0
  68. data/lib/zmq/socket/stream.rb +44 -0
  69. data/lib/zmq/version.rb +1 -1
  70. data/test/socket/test_dealer_socket.rb +1 -1
  71. data/test/socket/test_pair_socket.rb +1 -1
  72. data/test/socket/test_pair_sockets.rb +1 -1
  73. data/test/socket/test_pub_socket.rb +1 -1
  74. data/test/socket/test_pub_sub_sockets.rb +1 -1
  75. data/test/socket/test_pull_socket.rb +1 -1
  76. data/test/socket/test_push_pull_sockets.rb +1 -1
  77. data/test/socket/test_push_socket.rb +1 -1
  78. data/test/socket/test_rep_socket.rb +1 -1
  79. data/test/socket/test_req_rep_sockets.rb +1 -1
  80. data/test/socket/test_req_socket.rb +1 -1
  81. data/test/socket/test_router_socket.rb +1 -1
  82. data/test/socket/test_routing.rb +1 -1
  83. data/test/socket/test_stream_socket.rb +74 -0
  84. data/test/socket/test_sub_socket.rb +1 -1
  85. data/test/test_beacon.rb +4 -2
  86. data/test/test_context.rb +2 -2
  87. data/test/test_frame.rb +2 -2
  88. data/test/test_handler.rb +2 -2
  89. data/test/test_logger.rb +1 -1
  90. data/test/test_loop.rb +2 -2
  91. data/test/test_message.rb +1 -1
  92. data/test/test_monitoring.rb +15 -3
  93. data/test/test_poller.rb +2 -2
  94. data/test/test_pollitem.rb +2 -2
  95. data/test/test_socket.rb +53 -6
  96. data/test/test_threading.rb +2 -2
  97. data/test/test_timer.rb +2 -2
  98. data/test/test_zmq.rb +2 -2
  99. metadata +109 -104
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), '..', 'helper')
3
+ require File.expand_path("../../helper.rb", __FILE__)
4
4
 
5
5
  class TestPubSocket < ZmqTestCase
6
6
  def test_behavior
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), '..', 'helper')
3
+ require File.expand_path("../../helper.rb", __FILE__)
4
4
 
5
5
  class TestPubSubSockets < ZmqTestCase
6
6
  def test_flow
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), '..', 'helper')
3
+ require File.expand_path("../../helper.rb", __FILE__)
4
4
 
5
5
  class TestPullSocket < ZmqTestCase
6
6
  def test_behavior
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), '..', 'helper')
3
+ require File.expand_path("../../helper.rb", __FILE__)
4
4
 
5
5
  class TestPushPullSockets < ZmqTestCase
6
6
  def test_flow
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), '..', 'helper')
3
+ require File.expand_path("../../helper.rb", __FILE__)
4
4
 
5
5
  class TestPushSocket < ZmqTestCase
6
6
  def test_behavior
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), '..', 'helper')
3
+ require File.expand_path("../../helper.rb", __FILE__)
4
4
 
5
5
  class TestRepSocket < ZmqTestCase
6
6
  def test_behavior
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), '..', 'helper')
3
+ require File.expand_path("../../helper.rb", __FILE__)
4
4
 
5
5
  class TestReqRepSockets < ZmqTestCase
6
6
  def test_flow
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), '..', 'helper')
3
+ require File.expand_path("../../helper.rb", __FILE__)
4
4
 
5
5
  class TestReqSocket < ZmqTestCase
6
6
  def test_behavior
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), '..', 'helper')
3
+ require File.expand_path("../../helper.rb", __FILE__)
4
4
 
5
5
  class TestRouterSocket < ZmqTestCase
6
6
  def test_behavior
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), '..', 'helper')
3
+ require File.expand_path("../../helper.rb", __FILE__)
4
4
 
5
5
  class TestRouting < ZmqTestCase
6
6
  def test_flow
@@ -0,0 +1,74 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path("../../helper.rb", __FILE__)
4
+ require 'socket'
5
+ require 'timeout'
6
+
7
+ class TestStreamSocket < ZmqTestCase
8
+ def test_behavior
9
+ ctx = ZMQ::Context.new
10
+ sock = ctx.socket(:STREAM)
11
+ assert_equal ZMQ::STREAM, sock.type
12
+ assert_equal "STREAM socket", sock.to_s
13
+ ensure
14
+ ctx.destroy
15
+ end
16
+
17
+ def test_recv_and_send
18
+ ctx = ZMQ::Context.new
19
+ sock = ctx.socket(:STREAM)
20
+ port = sock.bind("tcp://127.0.0.1:*")
21
+
22
+ tcp = TCPSocket.new('127.0.0.1', port)
23
+ tcp.write("hello")
24
+
25
+ Timeout.timeout(5) do
26
+ msg = sock.recv_message
27
+
28
+ # Messages received from a STREAM socket are in two parts:
29
+ # The first frame is an identiy frame.
30
+ # The second frame is the data received over the TCP socket.
31
+ assert_equal 2, msg.size
32
+
33
+ # first frame is the identity frame.
34
+ identity = msg.pop
35
+ assert_equal "hello", msg.first.to_s
36
+
37
+ sock.sendm(identity)
38
+ sock.send("goodbye")
39
+
40
+ reply = tcp.recv(100)
41
+ assert_equal "goodbye", reply
42
+ end
43
+
44
+ ensure
45
+ tcp.close if tcp
46
+ ctx.destroy
47
+ end
48
+
49
+ # This test should work, but may be a bug in zmq. leaving out for now:
50
+ # def test_close_tcp_connection
51
+ # ctx = ZMQ::Context.new
52
+ # sock = ctx.socket(:STREAM)
53
+ # port = sock.bind("tcp://127.0.0.1:*")
54
+ #
55
+ # tcp = TCPSocket.new('127.0.0.1', port)
56
+ # tcp.write("hello")
57
+ #
58
+ # Timeout.timeout(5) do
59
+ # identity = sock.recv
60
+ # message = sock.recv
61
+ #
62
+ # sock.sendm identity
63
+ # sock.send ""
64
+ #
65
+ # # receiving a zero length string is a TCP end of stream = closed normally.
66
+ # assert_equal "", tcp.recvfrom(100)
67
+ # end
68
+ #
69
+ # ensure
70
+ # tcp.close if tcp
71
+ # ctx.destroy
72
+ # end
73
+
74
+ end
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), '..', 'helper')
3
+ require File.expand_path("../../helper.rb", __FILE__)
4
4
 
5
5
  class TestSubSocket < ZmqTestCase
6
6
  def test_behavior
@@ -1,10 +1,11 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), 'helper')
3
+ require File.expand_path("../helper.rb", __FILE__)
4
4
  require 'socket'
5
5
 
6
6
  class TestZmqBeacon < ZmqTestCase
7
7
  def setup
8
+ GC.start
8
9
  r = Random.new
9
10
  begin
10
11
  # find a random port number that we are able to bind to, and use this port
@@ -78,6 +79,7 @@ class TestZmqBeacon < ZmqTestCase
78
79
  end
79
80
 
80
81
  def test_pipe
82
+ GC.start
81
83
  beacon = ZMQ::Beacon.new(@port)
82
84
  assert_instance_of ZMQ::Socket::Pair, beacon.pipe
83
85
  GC.start # check GC cycle with "detached" socket
@@ -106,4 +108,4 @@ class TestZmqBeacon < ZmqTestCase
106
108
  service_beacon.destroy
107
109
  client_beacon.destroy
108
110
  end
109
- end
111
+ end
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), 'helper')
3
+ require File.expand_path("../helper.rb", __FILE__)
4
4
 
5
5
  class TestZmqContext < ZmqTestCase
6
6
  def test_context
@@ -83,4 +83,4 @@ class TestZmqContext < ZmqTestCase
83
83
  ensure
84
84
  ctx.destroy
85
85
  end
86
- end
86
+ end
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), 'helper')
3
+ require File.expand_path("../helper.rb", __FILE__)
4
4
 
5
5
  class TestZmqFrame < ZmqTestCase
6
6
  def test_alloc
@@ -74,4 +74,4 @@ class TestZmqFrame < ZmqTestCase
74
74
  frame.reset("msg")
75
75
  assert_equal "msg", frame.data
76
76
  end
77
- end
77
+ end
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), 'helper')
3
+ require File.expand_path("../helper.rb", __FILE__)
4
4
 
5
5
  class TestZmqHandler < ZmqTestCase
6
6
  def test_expects_pollitem
@@ -25,4 +25,4 @@ class TestZmqHandler < ZmqTestCase
25
25
  handler.on_error(StandardError.new)
26
26
  end
27
27
  end
28
- end
28
+ end
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), 'helper')
3
+ require File.expand_path("../helper.rb", __FILE__)
4
4
 
5
5
  class TestZmqLogger < ZmqTestCase
6
6
 
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), 'helper')
3
+ require File.expand_path("../helper.rb", __FILE__)
4
4
 
5
5
  class TestZmqLoop < ZmqTestCase
6
6
  def test_alloc
@@ -249,4 +249,4 @@ class TestZmqLoop < ZmqTestCase
249
249
  ensure
250
250
  ctx.destroy
251
251
  end
252
- end
252
+ end
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), 'helper')
3
+ require File.expand_path("../helper.rb", __FILE__)
4
4
 
5
5
  class TestZmqMessage < ZmqTestCase
6
6
  def test_message
@@ -1,9 +1,9 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), 'helper')
3
+ require File.expand_path("../helper.rb", __FILE__)
4
4
 
5
5
  class TestMonitor
6
- attr_reader :listening, :closed
6
+ attr_reader :listening, :closed, :accepted
7
7
 
8
8
  def on_listening(addr, fd)
9
9
  @listening = true
@@ -12,6 +12,14 @@ class TestMonitor
12
12
  def on_closed(addr, fd)
13
13
  @closed = true
14
14
  end
15
+
16
+ def on_accepted(addr, fd)
17
+ @accepted = true
18
+ end
19
+
20
+ def on_bind_failed(addr, fd)
21
+ @bind_failed = true
22
+ end
15
23
  end
16
24
 
17
25
  class TestZmqMonitoring < ZmqTestCase
@@ -31,9 +39,13 @@ class TestZmqMonitoring < ZmqTestCase
31
39
  assert !sock.monitor("tcp://0.0.0.0:5000")
32
40
  assert sock.monitor("inproc://monitor.rep", cb)
33
41
  sleep 1
34
- sock.bind("tcp://0.0.0.0:5555")
42
+ port = sock.bind("tcp://127.0.0.1:*")
35
43
  sleep 1
36
44
  assert cb.listening
45
+ client = ctx.socket(:REQ)
46
+ client.connect("tcp://127.0.0.1:#{port}")
47
+ sleep 1
48
+ assert cb.accepted
37
49
  sock.close
38
50
  sleep 1
39
51
  assert cb.closed
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), 'helper')
3
+ require File.expand_path("../helper.rb", __FILE__)
4
4
 
5
5
  class TestZmqPoller < ZmqTestCase
6
6
 
@@ -151,4 +151,4 @@ class TestZmqPoller < ZmqTestCase
151
151
  poller = ZMQ::Poller.new
152
152
  assert_equal [], poller.writables
153
153
  end
154
- end
154
+ end
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), 'helper')
3
+ require File.expand_path("../helper.rb", __FILE__)
4
4
 
5
5
  class TestZmqPollitem < ZmqTestCase
6
6
  def test_alloc_io
@@ -85,4 +85,4 @@ class TestZmqPollitem < ZmqTestCase
85
85
  ensure
86
86
  ctx.destroy
87
87
  end
88
- end
88
+ end
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), 'helper')
3
+ require File.expand_path("../helper.rb", __FILE__)
4
4
 
5
5
  class TestZmqSocket < ZmqTestCase
6
6
  def test_recv_timeout
@@ -183,13 +183,13 @@ class TestZmqSocket < ZmqTestCase
183
183
  assert(req.state & ZMQ::Socket::PENDING)
184
184
  req.connect("tcp://127.0.0.1:#{port}")
185
185
  assert_equal "PAIR socket", sock.to_s
186
- assert_equal "REP socket bound to tcp://127.0.0.1:*", rep.to_s
186
+ assert_equal "REP socket bound to tcp://127.0.0.1:#{port}", rep.to_s
187
187
  assert_equal "REQ socket connected to tcp://127.0.0.1:#{port}", req.to_s
188
188
 
189
189
 
190
190
  port2 = rep.bind("tcp://127.0.0.1:*")
191
191
  req.connect("tcp://127.0.0.1:#{port2}")
192
- assert_equal "REP socket bound to tcp://127.0.0.1:*, tcp://127.0.0.1:*", rep.to_s
192
+ assert_equal "REP socket bound to tcp://127.0.0.1:#{port}, tcp://127.0.0.1:#{port2}", rep.to_s
193
193
  assert_equal "REQ socket connected to tcp://127.0.0.1:#{port}, tcp://127.0.0.1:#{port2}", req.to_s
194
194
  ensure
195
195
  ctx.destroy
@@ -201,7 +201,7 @@ class TestZmqSocket < ZmqTestCase
201
201
  port = rep.bind("tcp://127.0.0.1:*")
202
202
  req = ctx.socket(:REQ)
203
203
  req.connect("tcp://127.0.0.1:#{port}")
204
- assert_equal "tcp://127.0.0.1:*", rep.endpoint
204
+ assert_equal "tcp://127.0.0.1:#{port}", rep.endpoint
205
205
  assert_equal "tcp://127.0.0.1:#{port}", req.endpoint
206
206
  ensure
207
207
  ctx.destroy
@@ -213,12 +213,12 @@ class TestZmqSocket < ZmqTestCase
213
213
  port = rep.bind("tcp://127.0.0.1:*")
214
214
  req = ctx.socket(:REQ)
215
215
  req.connect("tcp://127.0.0.1:#{port}")
216
- assert_equal ["tcp://127.0.0.1:*"], rep.endpoints
216
+ assert_equal ["tcp://127.0.0.1:#{port}"], rep.endpoints
217
217
  assert_equal ["tcp://127.0.0.1:#{port}"], req.endpoints
218
218
 
219
219
  port2 = rep.bind("tcp://127.0.0.1:*")
220
220
  req.connect("tcp://127.0.0.1:#{port2}")
221
- assert_equal ["tcp://127.0.0.1:*", "tcp://127.0.0.1:*"], rep.endpoints
221
+ assert_equal ["tcp://127.0.0.1:#{port}", "tcp://127.0.0.1:#{port2}"], rep.endpoints
222
222
  assert_equal ["tcp://127.0.0.1:#{port}", "tcp://127.0.0.1:#{port2}"], req.endpoints
223
223
  ensure
224
224
  ctx.destroy
@@ -558,4 +558,51 @@ end
558
558
  ensure
559
559
  ctx.destroy
560
560
  end
561
+
562
+ def test_last_endpoint
563
+ ctx = ZMQ::Context.new
564
+ sock = ctx.socket(ZMQ::PULL)
565
+ port = sock.bind('tcp://127.0.0.1:*')
566
+ assert_equal sock.last_endpoint, "tcp://127.0.0.1:#{port}"
567
+ ensure
568
+ ctx.destroy
569
+ end
570
+
571
+ def test_ephemeral_bind
572
+ ctx = ZMQ::Context.new
573
+ sock = ctx.socket(ZMQ::PULL)
574
+ port = sock.bind('tcp://127.0.0.1:*')
575
+ assert sock.endpoints.include?("tcp://127.0.0.1:#{port}")
576
+ ensure
577
+ ctx.destroy
578
+ end
579
+
580
+ def test_ephemral_bind_and_unbind
581
+ ctx = ZMQ::Context.new
582
+ sock = ctx.socket(ZMQ::PULL)
583
+ port = sock.bind('tcp://127.0.0.1:*')
584
+ sock.unbind("tcp://127.0.0.1:#{port}")
585
+ assert sock.endpoints.count == 0
586
+ ensure
587
+ ctx.destroy
588
+ end
589
+
590
+ def test_pollable_after_bind_and_unbind
591
+ ctx = ZMQ::Context.new
592
+ router = ctx.socket(ZMQ::ROUTER)
593
+ port1 = router.bind('tcp://127.0.0.1:*')
594
+ dealer = ctx.socket(ZMQ::DEALER)
595
+ dealer.connect(router.last_endpoint)
596
+ port2 = router.bind('tcp://127.0.0.1:*')
597
+ dealer.send("hello")
598
+ sleep(0.01)
599
+ assert_equal router.poll(0), true
600
+ router.unbind("tcp://127.0.0.1:#{port2}")
601
+ assert_equal router.poll(0), true
602
+ message = router.recv_message
603
+ assert_equal message.last.to_s, "hello"
604
+ ensure
605
+ ctx.destroy
606
+ end
607
+
561
608
  end
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.join(File.dirname(__FILE__), 'helper')
3
+ require File.expand_path("../helper.rb", __FILE__)
4
4
 
5
5
  Thread.abort_on_exception = true
6
6
 
@@ -31,4 +31,4 @@ class TestZmqThreading < ZmqTestCase
31
31
  thread_vals = [0, 1, 2, 3, 4, 500_000, 500_000, 500_000, 500_000, 500_000, expected, true]
32
32
  assert_equal thread_vals, threads
33
33
  end
34
- end
34
+ end