uringmachine 0.29.1 → 0.29.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 248a2f0e2a780904f26f0183d08237039e18b76b736e7b3563c2699e30041d65
4
- data.tar.gz: 2020b0c8cc9be25becddef80fa4c66f0bc49e30d0791ec2b2b2e618fbf3ab0b6
3
+ metadata.gz: 6151ac8411facd0ef37fa66a7cae623d2137f335bbe1e178529cb0a76c9f56c3
4
+ data.tar.gz: 5918fde9ea4ba936760a3d402207bd6fd5806337b005cd3633ab1cf0f8584ed0
5
5
  SHA512:
6
- metadata.gz: 543a8adf2c628f080f9ec249e11ff7e4bd969f0eca9e47285ba0ef20c28575ddd6d1902a3890d49ccb1ccf5100d13cd721bbd03c472bde15205344524fd7795c
7
- data.tar.gz: 4afbe860f29187507c57a8e319d02f2f843d27aa7d65176f0e0040914bf4d8e68ce894d4663b13d3ace530bf2aa4fe3ca547e58d5cbcb1daf5d89f6d5bb94ae2
6
+ metadata.gz: 0d77a7f37251930db32ec8023b1cb60e5595348544b279b175d39a528dbe782a703c75f6ebde5b2dd84379ec12ae83decf47f11f0b692e4975a36f146b4bc975
7
+ data.tar.gz: 190d3d88d32f91241c3c9ed3fc674d5318fcf630e64d5d5fdecdc7a86b6e62f03786190a021f4828e6a40022ccbf301a3cffdf0311ff35bc747889d9f00fbb9b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # 0.29.2 2026-03-15
2
+
3
+ - Add `Stream#consumed`, `Stream#pending`
4
+ - Add `UM#stream`
5
+ - Add `Stream#skip`
6
+
1
7
  # 0.29.1 2026-03-14
2
8
 
3
9
  - Add support for exception instance in `#timeout`
data/README.md CHANGED
@@ -32,11 +32,13 @@ implementation that allows integration with the entire Ruby ecosystem.
32
32
  ## Features
33
33
 
34
34
  - Automatic fiber switching when performing blocking I/O operations.
35
- - Automatic cancellation using of ongoing operations with Ruby exceptions.
35
+ - Automatic cancellation ongoing operations with Ruby exceptions.
36
36
  - General-purpose API for cancelling any operation on timeout.
37
37
  - Excellent performance characteristics for concurrent I/O-bound applications.
38
38
  - `Fiber::Scheduler` implementation to automatically integrate with the Ruby
39
39
  ecosystem in a transparent fashion.
40
+ - Read streams with automatic buffer management.
41
+ - Optimized I/O for encrypted SSL connections.
40
42
 
41
43
  ## Design
42
44
 
@@ -61,8 +63,8 @@ allow any library that performs I/O using standard-library classes such as `IO`,
61
63
 
62
64
  To install UringMachine, simply run `gem install uringmachine` or `bundle add
63
65
  uringmachine` in your project directory. Note: to use UringMachine, you'll need
64
- a Linux machine with a minimum kernel version of 6.7. Some features require
65
- newer kernel versions.
66
+ a Linux machine with a minimum Linux kernel version of 6.7. Some features
67
+ require newer kernel versions.
66
68
 
67
69
  To perform I/O using UringMachine, simply create an instance:
68
70
 
@@ -79,7 +81,7 @@ You can perform I/O by directly making method calls such as `write` or `read`
79
81
  ```ruby
80
82
  # Most UringMachine instance methods will need you to provide a file descriptor.
81
83
  # Here we print a message to STDOUT. Note the explicit line break:
82
- machine.write(STDOUT, "Hello, world!\n")
84
+ machine.write(UM::STDOUT_FILENO, "Hello, world!\n")
83
85
  ```
84
86
 
85
87
  UringMachine provides an I/O interface that is to a large degree equivalent to
@@ -91,14 +93,14 @@ the Unix standard C interface:
91
93
  fd = machine.open('foo.txt', UM::O_RDONLY)
92
94
  buf = +''
93
95
  size = machine.read(fd, buf, 8192)
94
- machine.write(STDOUT, "File content: #{buf.inspect}")
96
+ machine.write(UM::STDOUT_FILENO, "File content: #{buf.inspect}")
95
97
  machine.close(fd)
96
98
 
97
99
  # Or alternatively (with automatic file closing):
98
100
  machine.open('foo.txt', UM::O_RDONLY) do |fd|
99
101
  buf = +''
100
102
  size = machine.read(fd, buf, 8192)
101
- machine.write(STDOUT, "File content: #{buf.inspect}")
103
+ machine.write(UM::STDOUT_FILENO, "File content: #{buf.inspect}")
102
104
  end
103
105
  ```
104
106
 
@@ -284,6 +286,104 @@ fiber = Fiber.schedule do
284
286
  end
285
287
  ```
286
288
 
289
+ ## Read Streams
290
+
291
+ A UringMachine stream is used to efficiently read from a socket or other file
292
+ descriptor. Streams are ideal for implementing the read side of protocols, and
293
+ provide an API that is useful for both line-based protocols and binary
294
+ (frame-based) protocols.
295
+
296
+ A stream is associated with a UringMachine instance and a target file descriptor
297
+ (see also [stream modes](#stream-modes) below). Behind the scenes, streams take
298
+ advantage of io_uring's registered buffers feature, and more recently, the
299
+ introduction of [incremental buffer
300
+ consumption](https://github.com/axboe/liburing/wiki/What's-new-with-io_uring-in-6.11-and-6.12#incremental-provided-buffer-consumption).
301
+
302
+ When streams are used, UringMachine automatically manages the buffers it
303
+ provides to the kernel, maximizing buffer reuse and minimizing allocations.
304
+ UringMachine also responds to stress conditions (increased incoming traffic) by
305
+ automatically provisioning additional buffers.
306
+
307
+ To create a stream for a given fd, use `UM#stream`:
308
+
309
+ ```ruby
310
+ stream = machine.stream(fd)
311
+
312
+ # you can also provide a block that will be passed the stream instance:
313
+ machine.stream(fd) { |s| do_something_with(s) }
314
+
315
+ # you can also instantiate a stream directly:
316
+ stream = UM::Stream.new(machine, fd)
317
+ ```
318
+
319
+ The following API is used to interact with the stream:
320
+
321
+ ```ruby
322
+ # Read until a newline character is encountered:
323
+ line = stream.get_line(0)
324
+
325
+ # Read line with a maximum length of 13 bytes:
326
+ line = stream.get_line(13)
327
+
328
+ # Read all data:
329
+ buf = stream.get_string(0)
330
+
331
+ # Read exactly 13 bytes:
332
+ buf = stream.get_string(13)
333
+
334
+ # Read up to 13 bytes:
335
+ buf = stream.get_string(-13)
336
+
337
+ # Skip 3 bytes:
338
+ stream.skip(3)
339
+ ```
340
+
341
+ Here's an example of a how a basic HTTP request parser might be implemented
342
+ using a stream:
343
+
344
+ ```ruby
345
+ def parse_http_request_headers(stream)
346
+ request_line = stream.get_line(0)
347
+ m = request_line.match(REQUEST_LINE_RE)
348
+ return nil if !m
349
+
350
+ headers = {
351
+ ':method' => m[1],
352
+ ':path' => m[2],
353
+ ':protocol' => m[3]
354
+ }
355
+
356
+ while true
357
+ line = stream.get_line(0)
358
+ break if !line || line.empty?
359
+
360
+ m = line.match(HEADER_RE)
361
+ headers[m[1].downcase] = m[2]
362
+ end
363
+ headers
364
+ end
365
+ ```
366
+
367
+ ### Stream modes
368
+
369
+ Stream modes allow streams to be transport agnostic. Currently streams support
370
+ three modes:
371
+
372
+ - `:bp_read` - use the buffer pool, read data using multishot read
373
+ (this is the default mode).
374
+ - `:bp_recv` - use the buffer pool, read data using multishot recv.
375
+ - `:ssl` - read from an `SSLSocket` object.
376
+
377
+ The mode is specified as an additional argument to `Stream.new`:
378
+
379
+ ```ruby
380
+ # stream using recv:
381
+ stream = machine.stream(fd, :bp_recv)
382
+
383
+ # stream on an SSL socket:
384
+ stream = machine.stream(ssl, :ssl)
385
+ ```
386
+
287
387
  ## Performance
288
388
 
289
389
  [Detailed benchmarks](benchmark/README.md)
data/TODO.md CHANGED
@@ -1,6 +1,5 @@
1
1
  ## immediate
2
2
 
3
- - Add support for exception instances in `#timeout`.
4
3
  - Add support for returning a value on timeout
5
4
 
6
5
  Since to do this safely we need to actually raise an exception that wraps the
@@ -57,7 +57,7 @@ buffers, to using managed buffers from the buffer pool.
57
57
 
58
58
  - The buffer pool is created and managed automatically. No API is involved.
59
59
  -
60
- -
60
+ -
61
61
 
62
62
  - To use the buffer pool, two dedicated APIs are added:
63
63
 
data/ext/um/um.h CHANGED
@@ -283,7 +283,8 @@ struct um_stream {
283
283
  struct um_op *op;
284
284
  struct um_segment *head;
285
285
  struct um_segment *tail;
286
- size_t pending_len;
286
+ size_t consumed_bytes;
287
+ size_t pending_bytes;
287
288
  size_t pos;
288
289
  int eof;
289
290
  };
data/ext/um/um_stream.c CHANGED
@@ -9,7 +9,7 @@ inline void stream_add_segment(struct um_stream *stream, struct um_segment *segm
9
9
  }
10
10
  else
11
11
  stream->head = stream->tail = segment;
12
- stream->pending_len += segment->len;
12
+ stream->pending_bytes += segment->len;
13
13
  }
14
14
 
15
15
  inline int stream_process_op_result(struct um_stream *stream, struct um_op_result *result) {
@@ -73,7 +73,7 @@ void um_stream_cleanup(struct um_stream *stream) {
73
73
  um_segment_checkin(stream->machine, stream->head);
74
74
  stream->head = next;
75
75
  }
76
- stream->pending_len = 0;
76
+ stream->pending_bytes = 0;
77
77
  }
78
78
 
79
79
  // returns true if case of ENOBUFS error, sets more to true if more data forthcoming
@@ -124,7 +124,7 @@ void stream_clear(struct um_stream *stream) {
124
124
  um_segment_checkin(stream->machine, stream->head);
125
125
  stream->head = next;
126
126
  }
127
- stream->pending_len = 0;
127
+ stream->pending_bytes = 0;
128
128
 
129
129
  if (stream->working_buffer) {
130
130
  bp_buffer_checkin(stream->machine, stream->working_buffer);
@@ -156,12 +156,12 @@ int stream_get_more_segments_bp(struct um_stream *stream) {
156
156
  enobufs = stream_process_segments(stream, &total_bytes, &more);
157
157
  um_op_multishot_results_clear(stream->machine, stream->op);
158
158
  if (unlikely(enobufs)) {
159
- int should_restart = stream->pending_len < (stream->machine->bp_buffer_size * 4);
159
+ int should_restart = stream->pending_bytes < (stream->machine->bp_buffer_size * 4);
160
160
  // int same_threshold = stream->op->bp_commit_level == stream->machine->bp_commit_level;
161
161
 
162
162
  // fprintf(stderr, "%p enobufs total: %ld pending: %ld threshold: %ld bc: %d (same: %d, restart: %d)\n",
163
163
  // stream,
164
- // total_bytes, stream->pending_len, stream->machine->bp_commit_level,
164
+ // total_bytes, stream->pending_bytes, stream->machine->bp_commit_level,
165
165
  // stream->machine->bp_buffer_count,
166
166
  // same_threshold, should_restart
167
167
  // );
@@ -173,7 +173,7 @@ int stream_get_more_segments_bp(struct um_stream *stream) {
173
173
  if (should_restart) {
174
174
  if (stream->op->bp_commit_level == stream->machine->bp_commit_level)
175
175
  bp_handle_enobufs(stream->machine);
176
-
176
+
177
177
  um_op_release(stream->machine, stream->op);
178
178
  stream->op = NULL;
179
179
  // stream_multishot_op_start(stream);
@@ -235,12 +235,16 @@ inline void stream_shift_head(struct um_stream *stream) {
235
235
  }
236
236
 
237
237
  inline void stream_skip(struct um_stream *stream, size_t inc, int safe_inc) {
238
+ if (unlikely(stream->eof && !stream->head)) return;
239
+ if (safe_inc && !stream->tail && !stream_get_more_segments(stream)) return;
240
+
238
241
  while (inc) {
239
242
  size_t segment_len = stream->head->len - stream->pos;
240
243
  size_t inc_len = (segment_len <= inc) ? segment_len : inc;
241
244
  inc -= inc_len;
242
245
  stream->pos += inc_len;
243
- stream->pending_len -= inc_len;
246
+ stream->consumed_bytes += inc_len;
247
+ stream->pending_bytes -= inc_len;
244
248
  if (stream->pos == stream->head->len) {
245
249
  stream_shift_head(stream);
246
250
  if (inc && safe_inc && !stream->head) {
@@ -259,7 +263,8 @@ inline void stream_copy(struct um_stream *stream, char *dest, size_t len) {
259
263
 
260
264
  len -= cpy_len;
261
265
  stream->pos += cpy_len;
262
- stream->pending_len -= cpy_len;
266
+ stream->consumed_bytes += cpy_len;
267
+ stream->pending_bytes -= cpy_len;
263
268
  dest += cpy_len;
264
269
  if (stream->pos == stream->head->len) stream_shift_head(stream);
265
270
  }
@@ -102,6 +102,20 @@ static inline void stream_setup(struct um_stream *stream, VALUE target, VALUE mo
102
102
  rb_raise(eUMError, "Invalid stream mode");
103
103
  }
104
104
 
105
+ /* call-seq:
106
+ * UM::Stream.new(machine, fd, mode = nil) -> stream
107
+ * machine.stream(fd, mode = nil) -> stream
108
+ * machine.stream(fd, mode = nil) { |stream| ... }
109
+ *
110
+ * Initializes a new stream with the given UringMachine instance, target and
111
+ * optional mode. The target maybe a file descriptor, or an instance of
112
+ * OpenSSL::SSL::SSLSocket. In case of an SSL socket, the mode should be :ssl.
113
+ *
114
+ * @param machine [UringMachine] UringMachine instance
115
+ * @param target [integer, OpenSSL::SSL::SSLSocket] stream target: file descriptor or SSL socket
116
+ * @param mode [Symbol] optional stream mode: :bp_read, :bp_recv, :ssl
117
+ * @return [void]
118
+ */
105
119
  VALUE Stream_initialize(int argc, VALUE *argv, VALUE self) {
106
120
  VALUE machine;
107
121
  VALUE target;
@@ -118,6 +132,13 @@ VALUE Stream_initialize(int argc, VALUE *argv, VALUE self) {
118
132
  return self;
119
133
  }
120
134
 
135
+ /* call-seq:
136
+ * stream.mode -> mode
137
+ *
138
+ * Returns the stream mode.
139
+ *
140
+ * @return [Symbol] stream mode
141
+ */
121
142
  VALUE Stream_mode(VALUE self) {
122
143
  struct um_stream *stream = um_get_stream(self);
123
144
  switch (stream->mode) {
@@ -129,16 +150,57 @@ VALUE Stream_mode(VALUE self) {
129
150
  return Qnil;
130
151
  }
131
152
 
153
+ /* call-seq:
154
+ * stream.get_line(limit) -> str
155
+ *
156
+ * Reads from the string until a newline character is encountered. Returns the
157
+ * line without the newline delimiter. If limit is 0, the line length is not
158
+ * limited. If no newline delimiter is found before EOF, returns nil.
159
+ *
160
+ * @param limit [integer] maximum line length (0 means no limit)
161
+ * @return [String, nil] read data or nil
162
+ */
132
163
  VALUE Stream_get_line(VALUE self, VALUE limit) {
133
164
  struct um_stream *stream = um_get_stream(self);
134
165
  return stream_get_line(stream, Qnil, NUM2ULONG(limit));
135
166
  }
136
167
 
168
+ /* call-seq:
169
+ * stream.get_string(len) -> str
170
+ *
171
+ * Reads len bytes from the stream. If len is 0, reads all available bytes. If
172
+ * len is negative, reads up to -len available bytes. If len is positive and eof
173
+ * is encountered before len bytes are read, returns nil.
174
+ *
175
+ * @param len [integer] number of bytes to read
176
+ * @return [String, nil] read data or nil
177
+ */
137
178
  VALUE Stream_get_string(VALUE self, VALUE len) {
138
179
  struct um_stream *stream = um_get_stream(self);
139
180
  return stream_get_string(stream, Qnil, NUM2LONG(len), 0, false);
140
181
  }
141
182
 
183
+ /* call-seq:
184
+ * stream.skip(len) -> len
185
+ *
186
+ * Skips len bytes in the stream.
187
+ *
188
+ * @param len [integer] number of bytes to skip
189
+ * @return [Integer] len
190
+ */
191
+ VALUE Stream_skip(VALUE self, VALUE len) {
192
+ struct um_stream *stream = um_get_stream(self);
193
+ stream_skip(stream, NUM2LONG(len), true);
194
+ return len;
195
+ }
196
+
197
+ /* call-seq:
198
+ * stream.resp_decode -> obj
199
+ *
200
+ * Decodes an object from a RESP (Redis protocol) message.
201
+ *
202
+ * @return [any] decoded object
203
+ */
142
204
  VALUE Stream_resp_decode(VALUE self) {
143
205
  struct um_stream *stream = um_get_stream(self);
144
206
  VALUE out_buffer = rb_utf8_str_new_literal("");
@@ -147,6 +209,15 @@ VALUE Stream_resp_decode(VALUE self) {
147
209
  return obj;
148
210
  }
149
211
 
212
+ /* call-seq:
213
+ * stream.resp_encode(obj) -> string
214
+ *
215
+ * Encodes an object into a RESP (Redis protocol) message.
216
+ *
217
+ * @param str [String] string buffer
218
+ * @param obj [any] object to be encoded
219
+ * @return [String] str
220
+ */
150
221
  VALUE Stream_resp_encode(VALUE self, VALUE str, VALUE obj) {
151
222
  struct um_write_buffer buf;
152
223
  write_buffer_init(&buf, str);
@@ -156,11 +227,49 @@ VALUE Stream_resp_encode(VALUE self, VALUE str, VALUE obj) {
156
227
  return str;
157
228
  }
158
229
 
230
+ /* call-seq:
231
+ * stream.eof? -> bool
232
+ *
233
+ * Returns true if stream has reached EOF.
234
+ *
235
+ * @return [bool] EOF reached
236
+ */
159
237
  VALUE Stream_eof_p(VALUE self) {
160
238
  struct um_stream *stream = um_get_stream(self);
161
239
  return stream->eof ? Qtrue : Qfalse;
162
240
  }
163
241
 
242
+ /* call-seq:
243
+ * stream.consumed -> int
244
+ *
245
+ * Returns the total number of bytes consumed from the stream.
246
+ *
247
+ * @return [Integer] total bytes consumed
248
+ */
249
+ VALUE Stream_consumed(VALUE self) {
250
+ struct um_stream *stream = um_get_stream(self);
251
+ return LONG2NUM(stream->consumed_bytes);
252
+ }
253
+
254
+ /* call-seq:
255
+ * stream.pending -> int
256
+ *
257
+ * Returns the number of bytes available for reading.
258
+ *
259
+ * @return [Integer] bytes available
260
+ */
261
+ VALUE Stream_pending(VALUE self) {
262
+ struct um_stream *stream = um_get_stream(self);
263
+ return LONG2NUM(stream->pending_bytes);
264
+ }
265
+
266
+ /* call-seq:
267
+ * stream.clear -> stream
268
+ *
269
+ * Clears all available bytes and stops any ongoing read operation.
270
+ *
271
+ * @return [UM::Stream] self
272
+ */
164
273
  VALUE Stream_clear(VALUE self) {
165
274
  struct um_stream *stream = um_get_stream(self);
166
275
  stream_clear(stream);
@@ -176,11 +285,14 @@ void Init_Stream(void) {
176
285
 
177
286
  rb_define_method(cStream, "get_line", Stream_get_line, 1);
178
287
  rb_define_method(cStream, "get_string", Stream_get_string, 1);
288
+ rb_define_method(cStream, "skip", Stream_skip, 1);
179
289
 
180
290
  rb_define_method(cStream, "resp_decode", Stream_resp_decode, 0);
181
291
  rb_define_singleton_method(cStream, "resp_encode", Stream_resp_encode, 2);
182
292
 
183
293
  rb_define_method(cStream, "eof?", Stream_eof_p, 0);
294
+ rb_define_method(cStream, "consumed", Stream_consumed, 0);
295
+ rb_define_method(cStream, "pending", Stream_pending, 0);
184
296
  rb_define_method(cStream, "clear", Stream_clear, 0);
185
297
 
186
298
  eStreamRESPError = rb_define_class_under(cStream, "RESPError", rb_eStandardError);
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class UringMachine
4
- VERSION = '0.29.1'
4
+ VERSION = '0.29.2'
5
5
  end
data/lib/uringmachine.rb CHANGED
@@ -194,6 +194,15 @@ class UringMachine
194
194
  close_async(fd)
195
195
  end
196
196
 
197
+ def stream(target, mode = nil)
198
+ stream = UM::Stream.new(self, target, mode)
199
+ return stream if !block_given?
200
+
201
+ res = yield(stream)
202
+ stream.clear
203
+ res
204
+ end
205
+
197
206
  private
198
207
 
199
208
  # @param block [Proc]
data/test/test_stream.rb CHANGED
@@ -221,6 +221,16 @@ class StreamTest < StreamBaseTest
221
221
  assert_nil stream.get_string(-3)
222
222
  end
223
223
 
224
+ def test_stream_skip
225
+ machine.write(@wfd, "foobarbaz")
226
+
227
+ stream.skip(2)
228
+ assert_equal 'obar', stream.get_string(4)
229
+
230
+ stream.skip(1)
231
+ assert_equal 'az', stream.get_string(0)
232
+ end
233
+
224
234
  def test_stream_big_data
225
235
  data = SecureRandom.random_bytes(300_000)
226
236
  fiber = machine.spin {
@@ -593,3 +603,36 @@ class StreamModeTest < UMBaseTest
593
603
  sock2&.close rescue nil
594
604
  end
595
605
  end
606
+
607
+ class StreamByteCountsTest < StreamBaseTest
608
+ def test_stream_byte_counts
609
+ machine.write(@wfd, "foobar")
610
+
611
+ assert_equal 0, stream.consumed
612
+ assert_equal 0, stream.pending
613
+
614
+ buf = stream.get_string(2)
615
+ assert_equal 'fo', buf
616
+ assert_equal 2, stream.consumed
617
+ assert_equal 4, stream.pending
618
+
619
+ buf = stream.get_string(3)
620
+ assert_equal 'oba', buf
621
+ assert_equal 5, stream.consumed
622
+ assert_equal 1, stream.pending
623
+
624
+ machine.write(@wfd, "abc\ndef")
625
+ machine.snooze
626
+ assert_equal 5, stream.consumed
627
+ assert_equal 1, stream.pending
628
+
629
+ buf = stream.get_line(0)
630
+ assert_equal 'rabc', buf
631
+ assert_equal 10, stream.consumed
632
+ assert_equal 3, stream.pending
633
+
634
+ stream.clear
635
+ assert_equal 10, stream.consumed
636
+ assert_equal 0, stream.pending
637
+ end
638
+ end
data/test/test_um.rb CHANGED
@@ -3505,3 +3505,55 @@ class SetChildSubreaperTest < Minitest::Test
3505
3505
  assert_equal grand_child_pid, res
3506
3506
  end
3507
3507
  end
3508
+
3509
+ class StreamMethodTest < UMBaseTest
3510
+ def setup
3511
+ super
3512
+ @rfd, @wfd = UM.pipe
3513
+ end
3514
+
3515
+ def teardown
3516
+ @stream = nil
3517
+ machine.close(@rfd) rescue nil
3518
+ machine.close(@wfd) rescue nil
3519
+ super
3520
+ end
3521
+
3522
+ def test_stream_method
3523
+ machine.write(@wfd, "foobar")
3524
+ machine.close(@wfd)
3525
+
3526
+ stream = machine.stream(@rfd)
3527
+ assert_kind_of UM::Stream, stream
3528
+
3529
+ buf = stream.get_string(3)
3530
+ assert_equal 'foo', buf
3531
+
3532
+ buf = stream.get_string(-6)
3533
+ assert_equal 'bar', buf
3534
+ assert stream.eof?
3535
+
3536
+ stream.clear
3537
+ end
3538
+
3539
+ def test_stream_method_with_block
3540
+ machine.write(@wfd, "foobar")
3541
+ machine.close(@wfd)
3542
+
3543
+ bufs = []
3544
+ stream_obj = nil
3545
+ res = machine.stream(@rfd) do |s|
3546
+ stream_obj = s
3547
+
3548
+ bufs << s.get_string(3)
3549
+ bufs << s.get_string(-6)
3550
+
3551
+ :foo
3552
+ end
3553
+
3554
+ assert_kind_of UM::Stream, stream_obj
3555
+ assert stream_obj.eof?
3556
+ assert_equal ['foo', 'bar'], bufs
3557
+ assert_equal :foo, res
3558
+ end
3559
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uringmachine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.29.1
4
+ version: 0.29.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner