diff --git a/ext/-test-/memory_view/memory_view.c b/ext/-test-/memory_view/memory_view.c
index 4dee65dd3b9203..a61652eefc77c9 100644
--- a/ext/-test-/memory_view/memory_view.c
+++ b/ext/-test-/memory_view/memory_view.c
@@ -114,51 +114,85 @@ memory_view_parse_item_format(VALUE mod, VALUE format)
}
static VALUE
-memory_view_get_memory_view_info(VALUE mod, VALUE obj)
+memory_view_release_ensure(VALUE data)
{
- rb_memory_view_t view;
+ rb_memory_view_t *view = (rb_memory_view_t *)data;
+ rb_memory_view_release(view);
+ return Qnil;
+}
- if (!rb_memory_view_get(obj, &view, 0)) {
- return Qnil;
- }
+static VALUE
+memory_view_get_memory_view_info_body(VALUE args)
+{
+ rb_memory_view_t *view = (rb_memory_view_t *)args;
VALUE hash = rb_hash_new();
- rb_hash_aset(hash, sym_obj, view.obj);
- rb_hash_aset(hash, sym_byte_size, SSIZET2NUM(view.byte_size));
- rb_hash_aset(hash, sym_readonly, view.readonly ? Qtrue : Qfalse);
- rb_hash_aset(hash, sym_format, view.format ? rb_str_new_cstr(view.format) : Qnil);
- rb_hash_aset(hash, sym_item_size, SSIZET2NUM(view.item_size));
- rb_hash_aset(hash, sym_ndim, SSIZET2NUM(view.ndim));
-
- if (view.shape) {
- VALUE shape = rb_ary_new_capa(view.ndim);
+ rb_hash_aset(hash, sym_obj, view->obj);
+ rb_hash_aset(hash, sym_byte_size, SSIZET2NUM(view->byte_size));
+ rb_hash_aset(hash, sym_readonly, view->readonly ? Qtrue : Qfalse);
+ rb_hash_aset(hash, sym_format, view->format ? rb_str_new_cstr(view->format) : Qnil);
+ rb_hash_aset(hash, sym_item_size, SSIZET2NUM(view->item_size));
+ rb_hash_aset(hash, sym_ndim, SSIZET2NUM(view->ndim));
+
+ if (view->shape) {
+ VALUE shape = rb_ary_new_capa(view->ndim);
+ ssize_t i;
+ for (i = 0; i < view->ndim; i++) {
+ rb_ary_push(shape, SSIZET2NUM(view->shape[i]));
+ }
rb_hash_aset(hash, sym_shape, shape);
}
else {
rb_hash_aset(hash, sym_shape, Qnil);
}
- if (view.strides) {
- VALUE strides = rb_ary_new_capa(view.ndim);
+ if (view->strides) {
+ VALUE strides = rb_ary_new_capa(view->ndim);
+ ssize_t i;
+ for (i = 0; i < view->ndim; i++) {
+ rb_ary_push(strides, SSIZET2NUM(view->strides[i]));
+ }
rb_hash_aset(hash, sym_strides, strides);
}
else {
rb_hash_aset(hash, sym_strides, Qnil);
}
- if (view.sub_offsets) {
- VALUE sub_offsets = rb_ary_new_capa(view.ndim);
+ if (view->sub_offsets) {
+ VALUE sub_offsets = rb_ary_new_capa(view->ndim);
rb_hash_aset(hash, sym_sub_offsets, sub_offsets);
}
else {
rb_hash_aset(hash, sym_sub_offsets, Qnil);
}
- rb_memory_view_release(&view);
-
return hash;
}
+static VALUE
+memory_view_get_memory_view_info(int argc, VALUE *args, VALUE mod)
+{
+ rb_memory_view_t view;
+ VALUE obj;
+ VALUE flags;
+ enum ruby_memory_view_flags view_flags;
+
+ rb_scan_args(argc, args, "11", &obj, &flags);
+ if (NIL_P(flags)) {
+ view_flags = RUBY_MEMORY_VIEW_SIMPLE;
+ }
+ else {
+ view_flags = NUM2UINT(flags);
+ }
+
+ if (!rb_memory_view_get(obj, &view, view_flags)) {
+ return Qnil;
+ }
+
+ return rb_ensure(memory_view_get_memory_view_info_body, (VALUE)&view,
+ memory_view_release_ensure, (VALUE)&view);
+}
+
static VALUE
memory_view_is_row_major_contiguous(VALUE mod, VALUE obj)
{
@@ -281,6 +315,108 @@ memory_view_extract_item_members(VALUE mod, VALUE str, VALUE format)
return item;
}
+typedef struct {
+ VALUE location;
+ rb_memory_view_t view;
+} memory_view_get_data_args;
+
+static VALUE
+memory_view_get_data_body(VALUE _args)
+{
+ memory_view_get_data_args *args = (memory_view_get_data_args *)_args;
+ long beg, len;
+
+ if (RTEST(rb_range_beg_len(args->location,
+ &beg,
+ &len,
+ args->view.byte_size,
+ 0))) {
+ const char *content = ((const char *)(args->view.data)) + beg;
+ return rb_str_new(content, len);
+ }
+ else {
+ const char *content =
+ ((const char *)(args->view.data)) + NUM2LONG(args->location);
+ return rb_str_new(content, 1);
+ }
+}
+
+static VALUE
+memory_view_get_data(VALUE mod, VALUE obj, VALUE location)
+{
+ memory_view_get_data_args args;
+ args.location = location;
+
+ if (!rb_memory_view_get(obj, &(args.view), 0)) {
+ return Qnil;
+ }
+
+ return rb_ensure(memory_view_get_data_body, (VALUE)&args,
+ memory_view_release_ensure, (VALUE)&(args.view));
+}
+
+typedef struct {
+ VALUE offset;
+ VALUE data;
+ rb_memory_view_t view;
+} memory_view_set_data_args;
+
+static VALUE
+memory_view_set_data_body(VALUE _args)
+{
+ memory_view_set_data_args *args = (memory_view_set_data_args *)_args;
+
+ if (FIXNUM_P(args->data)) {
+ ((char *)(args->view.data))[NUM2LONG(args->offset)] =
+ NUM2LONG(args->data);
+ }
+ else {
+ StringValue(args->data);
+ memcpy(((char *)(args->view.data)) + NUM2LONG(args->offset),
+ RSTRING_PTR(args->data),
+ RSTRING_LEN(args->data));
+ }
+
+ return Qtrue;
+}
+
+static VALUE
+memory_view_set_data(VALUE mod, VALUE obj, VALUE offset, VALUE data)
+{
+ memory_view_set_data_args args;
+ args.offset = offset;
+ args.data = data;
+
+ if (!rb_memory_view_get(obj, &(args.view), RUBY_MEMORY_VIEW_WRITABLE)) {
+ return Qnil;
+ }
+
+ return rb_ensure(memory_view_set_data_body, (VALUE)&args,
+ memory_view_release_ensure, (VALUE)&(args.view));
+}
+
+static VALUE
+memory_view_get_body(VALUE data)
+{
+ return rb_yield_values(0);
+}
+
+static VALUE
+memory_view_get(VALUE mod, VALUE obj, VALUE flags)
+{
+ rb_memory_view_t view;
+
+ if (!rb_memory_view_get(obj, &view, NUM2UINT(flags))) {
+ rb_raise(rb_eArgError,
+ "Unable to get memory view: "
+ "object=%+" PRIsVALUE " flags=%+" PRIsVALUE,
+ obj, flags);
+ }
+
+ return rb_ensure(memory_view_get_body, Qnil,
+ memory_view_release_ensure, (VALUE)&view);
+}
+
static VALUE
expstr_initialize(VALUE obj, VALUE s)
{
@@ -416,16 +552,38 @@ Init_memory_view(void)
#ifdef HAVE_RUBY_MEMORY_VIEW_H
VALUE mMemoryViewTestUtils = rb_define_module("MemoryViewTestUtils");
+ rb_define_const(mMemoryViewTestUtils, "SIMPLE",
+ UINT2NUM(RUBY_MEMORY_VIEW_SIMPLE));
+ rb_define_const(mMemoryViewTestUtils, "WRITABLE",
+ UINT2NUM(RUBY_MEMORY_VIEW_WRITABLE));
+ rb_define_const(mMemoryViewTestUtils, "FORMAT",
+ UINT2NUM(RUBY_MEMORY_VIEW_FORMAT));
+ rb_define_const(mMemoryViewTestUtils, "MULTI_DIMENSIONAL",
+ UINT2NUM(RUBY_MEMORY_VIEW_MULTI_DIMENSIONAL));
+ rb_define_const(mMemoryViewTestUtils, "STRIDES",
+ UINT2NUM(RUBY_MEMORY_VIEW_STRIDES));
+ rb_define_const(mMemoryViewTestUtils, "ROW_MAJOR",
+ UINT2NUM(RUBY_MEMORY_VIEW_ROW_MAJOR));
+ rb_define_const(mMemoryViewTestUtils, "COLUMN_MAJOR",
+ UINT2NUM(RUBY_MEMORY_VIEW_COLUMN_MAJOR));
+ rb_define_const(mMemoryViewTestUtils, "ANY_CONTIGUOUS",
+ UINT2NUM(RUBY_MEMORY_VIEW_ANY_CONTIGUOUS));
+ rb_define_const(mMemoryViewTestUtils, "INDIRECT",
+ UINT2NUM(RUBY_MEMORY_VIEW_INDIRECT));
+
rb_define_module_function(mMemoryViewTestUtils, "available?", memory_view_available_p, 1);
rb_define_module_function(mMemoryViewTestUtils, "register", memory_view_register, 1);
rb_define_module_function(mMemoryViewTestUtils, "item_size_from_format", memory_view_item_size_from_format, 1);
rb_define_module_function(mMemoryViewTestUtils, "parse_item_format", memory_view_parse_item_format, 1);
- rb_define_module_function(mMemoryViewTestUtils, "get_memory_view_info", memory_view_get_memory_view_info, 1);
+ rb_define_module_function(mMemoryViewTestUtils, "get_memory_view_info", memory_view_get_memory_view_info, -1);
rb_define_module_function(mMemoryViewTestUtils, "is_row_major_contiguous", memory_view_is_row_major_contiguous, 1);
rb_define_module_function(mMemoryViewTestUtils, "is_column_major_contiguous", memory_view_is_column_major_contiguous, 1);
rb_define_module_function(mMemoryViewTestUtils, "fill_contiguous_strides", memory_view_fill_contiguous_strides, 4);
rb_define_module_function(mMemoryViewTestUtils, "ref_count_while_exporting", memory_view_ref_count_while_exporting, 2);
rb_define_module_function(mMemoryViewTestUtils, "extract_item_members", memory_view_extract_item_members, 2);
+ rb_define_module_function(mMemoryViewTestUtils, "get_data", memory_view_get_data, 2);
+ rb_define_module_function(mMemoryViewTestUtils, "set_data", memory_view_set_data, 3);
+ rb_define_module_function(mMemoryViewTestUtils, "get", memory_view_get, 2);
VALUE cExportableString = rb_define_class_under(mMemoryViewTestUtils, "ExportableString", rb_cObject);
rb_define_method(cExportableString, "initialize", expstr_initialize, 1);
diff --git a/file.c b/file.c
index d173cfe1858890..0f2cbfc9e64749 100644
--- a/file.c
+++ b/file.c
@@ -5536,21 +5536,22 @@ static VALUE rb_file_dirname_n(VALUE fname, int n);
/*
* call-seq:
- * File.dirname(file_name, level = 1) -> dir_name
+ * File.dirname(path, count = 1) -> string
*
- * Returns all components of the filename given in file_name
- * except the last one (after first stripping trailing separators).
- * The filename can be formed using both File::SEPARATOR and
- * File::ALT_SEPARATOR as the separator when File::ALT_SEPARATOR is
- * not nil.
+ * Returns a string path containing all but the last +count+ components
+ * of the given +path+:
*
- * File.dirname("/home/gumby/work/ruby.rb") #=> "/home/gumby/work"
+ * File.dirname('/usr/lib/linux') # => "/usr/lib"
+ * File.dirname('/usr') # => "/"
+ * File.dirname('/') # => "/"
+ * File.dirname('lib/') # => "."
+ * File.dirname('nosuch') # => "."
+ * File.dirname('/usr/lib/linux', 2) # => "/usr"
+ * File.dirname('/usr/lib/linux', 20) # => "/"
+ * File.dirname('/usr/lib/linux', 0) # => "/usr/lib/linux"
*
- * If +level+ is given, removes the last +level+ components, not only
- * one.
+ * Components are delimited by File::SEPARATOR and, if non-+nil+, File::ALT_SEPARATOR.
*
- * File.dirname("/home/gumby/work/ruby.rb", 2) #=> "/home/gumby"
- * File.dirname("/home/gumby/work/ruby.rb", 4) #=> "/"
*/
static VALUE
diff --git a/hash.c b/hash.c
index eede4d192108be..471d46049bc1eb 100644
--- a/hash.c
+++ b/hash.c
@@ -1497,7 +1497,7 @@ hash_slot_size(size_t capa, bool frozen)
}
static VALUE
-hash_alloc_capa(VALUE klass, VALUE flags, VALUE ifnone, size_t size, bool frozen)
+hash_alloc(VALUE klass, VALUE flags, VALUE ifnone, size_t size, bool frozen)
{
VALUE hash = rb_newobj_of(klass, T_HASH | flags, hash_slot_size(size, frozen));
rb_hash_set_ifnone(hash, ifnone);
@@ -1524,19 +1524,19 @@ hash_init_capa(VALUE hash, size_t size)
static VALUE
hash_hidden_new(size_t size)
{
- return hash_init_capa(hash_alloc_capa(0, 0, Qnil, size, false), size);
+ return hash_init_capa(hash_alloc(0, 0, Qnil, size, false), size);
}
-VALUE
-rb_hash_alloc_copy(VALUE klass, VALUE src)
+static VALUE
+hash_alloc_capa(VALUE klass, size_t size)
{
- return hash_alloc_capa(klass, 0, Qnil, RHASH_SIZE(src), false);
+ return hash_alloc(klass, 0, Qnil, size, false);
}
-static VALUE
-hash_alloc(VALUE klass)
+VALUE
+rb_hash_alloc_copy(VALUE klass, VALUE src)
{
- return hash_alloc_capa(klass, 0, Qnil, 0, false);
+ return hash_alloc_capa(klass, RHASH_SIZE(src));
}
#if USE_ZJIT
@@ -1553,7 +1553,7 @@ empty_hash_alloc(VALUE klass)
{
RUBY_DTRACE_CREATE_HOOK(HASH, 0);
- return hash_alloc(klass);
+ return hash_alloc_capa(klass, 0);
}
static VALUE
@@ -1568,7 +1568,7 @@ copy_compare_by_id(VALUE hash, VALUE basis)
static VALUE
hash_new_capa(VALUE klass, size_t capa)
{
- return hash_init_capa(hash_alloc_capa(klass, 0, Qnil, capa, false), capa);
+ return hash_init_capa(hash_alloc_capa(klass, capa), capa);
}
VALUE
@@ -1589,7 +1589,7 @@ rb_hash_new(void)
VALUE
rb_hash_alloc_fixed_size(VALUE klass, st_index_t size)
{
- return hash_init_capa(hash_alloc_capa(klass, 0, Qnil, size, true), size);
+ return hash_init_capa(hash_alloc(klass, 0, Qnil, size, true), size);
}
static VALUE
@@ -1635,7 +1635,7 @@ hash_copy(VALUE ret, VALUE hash)
static VALUE
hash_dup_with_compare_by_id(VALUE hash)
{
- VALUE dup = hash_alloc_capa(rb_cHash, 0, Qnil, RHASH_SIZE(hash), false);
+ VALUE dup = hash_alloc_capa(rb_cHash, RHASH_SIZE(hash));
if (RHASH_ST_TABLE_P(hash)) {
RHASH_SET_ST_FLAG(dup);
}
@@ -1646,14 +1646,14 @@ hash_dup_with_compare_by_id(VALUE hash)
static VALUE
hash_dup(VALUE hash, VALUE klass, VALUE flags)
{
- VALUE dup = hash_alloc_capa(klass, flags, RHASH_IFNONE(hash), RHASH_SIZE(hash), false);
+ VALUE dup = hash_alloc(klass, flags, RHASH_IFNONE(hash), RHASH_SIZE(hash), false);
return hash_copy(dup, hash);
}
static VALUE
hash_dup_capa(VALUE hash, size_t capa)
{
- VALUE ret = hash_alloc_capa(rb_cHash, 0, Qnil, capa, false);
+ VALUE ret = hash_alloc_capa(rb_cHash, capa);
if (capa > RHASH_AR_TABLE_MAX_SIZE) {
RHASH_SET_ST_FLAG(ret);
}
@@ -1933,7 +1933,7 @@ rb_hash_s_create(int argc, VALUE *argv, VALUE klass)
tmp = rb_hash_to_a(tmp);
}
else {
- hash = hash_alloc_capa(klass, 0, Qnil, RHASH_SIZE(tmp), false);
+ hash = hash_alloc_capa(klass, RHASH_SIZE(tmp));
return hash_copy(hash, tmp);
}
}
@@ -2123,7 +2123,7 @@ rb_hash_rehash(VALUE hash)
}
rb_hash_modify_check(hash);
if (RHASH_AR_TABLE_P(hash)) {
- tmp = hash_alloc(0);
+ tmp = hash_alloc_capa(0, RHASH_SIZE(hash));
rb_hash_foreach(hash, rb_hash_rehash_i, (VALUE)tmp);
hash_ar_free_and_clear_table(hash);
@@ -2131,7 +2131,7 @@ rb_hash_rehash(VALUE hash)
}
else if (RHASH_ST_TABLE_P(hash)) {
st_table *old_tab = RHASH_ST_TABLE(hash);
- tmp = hash_alloc(0);
+ tmp = hash_alloc_capa(0, 0);
hash_st_table_init(tmp, old_tab->type, old_tab->num_entries);
tbl = RHASH_ST_TABLE(tmp);
@@ -4813,7 +4813,7 @@ rb_hash_compare_by_id(VALUE hash)
else {
// Slow path: Need to rehash the members of `self` into a new
// `tmp` table using the new `identhash` compare/hash functions.
- tmp = hash_alloc(0);
+ tmp = hash_alloc_capa(0, 0);
hash_st_table_init(tmp, &identhash, RHASH_SIZE(hash));
identtable = RHASH_ST_TABLE(tmp);
diff --git a/inits.c b/inits.c
index 1be4916e87b357..e4323cc4a847b6 100644
--- a/inits.c
+++ b/inits.c
@@ -47,6 +47,7 @@ rb_call_inits(void)
CALL(marshal);
CALL(Range);
CALL(IO);
+ CALL(MemoryView); /* Must precede IO_Buffer */
CALL(IO_Buffer)
CALL(Dir);
CALL(Time);
@@ -70,7 +71,6 @@ rb_call_inits(void)
CALL(process);
CALL(Rational);
CALL(Complex);
- CALL(MemoryView);
CALL(pathname);
CALL(version);
CALL(vm_trace);
diff --git a/io.c b/io.c
index 5812aa4497d536..f4dcc9883d2f6b 100644
--- a/io.c
+++ b/io.c
@@ -8966,7 +8966,7 @@ io_puts_ary(VALUE ary, VALUE out, int recur)
* If called without arguments, writes a newline.
* See {Line IO}[rdoc-ref:IO@Line+IO].
*
- * Note that each added newline is the character "\n"/tt>,
+ * Note that each added newline is the character "\n",
* not the output record separator ($\\).
*
* Treatment for each object:
diff --git a/io_buffer.c b/io_buffer.c
index 5a5128aa1dd949..8099c23a37e6fe 100644
--- a/io_buffer.c
+++ b/io_buffer.c
@@ -8,6 +8,7 @@
#include "ruby/io/buffer.h"
#include "ruby/fiber/scheduler.h"
+#include "ruby/memory_view.h"
// For `rb_nogvl`.
#include "ruby/thread.h"
@@ -4252,6 +4253,81 @@ io_buffer_bit_count(int argc, VALUE *argv, VALUE self)
return SIZET2NUM(count);
}
+static bool
+io_buffer_memory_view_get(VALUE self, rb_memory_view_t *view, int flags)
+{
+ struct rb_io_buffer *buffer = get_io_buffer(self);
+
+ if (buffer->base == NULL || !io_buffer_validate(buffer)) {
+ return false;
+ }
+
+ bool readonly = true;
+ if (flags & RUBY_MEMORY_VIEW_WRITABLE) {
+ if (io_buffer_readonly_p(buffer)) {
+ return false;
+ } else {
+ readonly = false;
+ }
+ }
+ rb_memory_view_init_as_byte_array(view, self, buffer->base, buffer->size, readonly);
+ if (flags & RUBY_MEMORY_VIEW_FORMAT) {
+ view->format = "C";
+ }
+ bool request_multi_dimensional = flags & RUBY_MEMORY_VIEW_MULTI_DIMENSIONAL;
+ bool request_strides =
+ (flags & RUBY_MEMORY_VIEW_STRIDES) == RUBY_MEMORY_VIEW_STRIDES;
+ if (request_multi_dimensional || request_strides) {
+ size_t n_metadata = 0;
+ if (request_multi_dimensional)
+ n_metadata++;
+ if (request_strides)
+ n_metadata++;
+ ssize_t *metadata_buffer = ALLOC_N(ssize_t, n_metadata);
+ size_t i = 0;
+ if (request_multi_dimensional) {
+ ssize_t *shape = &metadata_buffer[i];
+ shape[0] = buffer->size;
+ view->shape = shape;
+ i++;
+ }
+ if (request_strides) {
+ ssize_t *strides = &metadata_buffer[i];
+ strides[0] = 1;
+ view->strides = strides;
+ i++;
+ }
+ view->private_data = metadata_buffer;
+ }
+ io_buffer_lock(buffer);
+
+ return true;
+}
+
+static bool
+io_buffer_memory_view_release(VALUE self, rb_memory_view_t *view)
+{
+ rb_io_buffer_unlock(self);
+ if (view->private_data) {
+ xfree(view->private_data);
+ }
+ return true;
+}
+
+static bool
+io_buffer_memory_view_available_p(VALUE self)
+{
+ struct rb_io_buffer *buffer = get_io_buffer(self);
+
+ return buffer->base != NULL && io_buffer_validate(buffer);
+}
+
+static const rb_memory_view_entry_t io_buffer_memory_view_entry = {
+ .get_func = io_buffer_memory_view_get,
+ .release_func = io_buffer_memory_view_release,
+ .available_p_func = io_buffer_memory_view_available_p,
+};
+
/*
* Document-class: IO::Buffer
*
@@ -4276,6 +4352,16 @@ io_buffer_bit_count(int argc, VALUE *argv, VALUE self)
* like Fiber::Scheduler#io_read and Fiber::Scheduler#io_write and parsing binary
* protocols.
*
+ * == MemoryView Support
+ *
+ * IO::Buffer supports the C-level MemoryView protocol, so C
+ * extensions can use +rb_memory_view_get()+ to access the buffer's
+ * memory directly (zero-copy) as a 1-dimensional contiguous array of
+ * bytes. The memory view is writable if the buffer is not
+ * #readonly? and +RUBY_MEMORY_VIEW_WRITABLE+ is specified.
+ *
+ * While a MemoryView is exported, the buffer is locked.
+ *
* == Examples of Usage
*
* Empty buffer:
@@ -4516,4 +4602,7 @@ Init_IO_Buffer(void)
rb_define_method(rb_cIOBuffer, "pread", io_buffer_pread, -1);
rb_define_method(rb_cIOBuffer, "write", io_buffer_write, -1);
rb_define_method(rb_cIOBuffer, "pwrite", io_buffer_pwrite, -1);
+
+ // MemoryView:
+ rb_memory_view_register(rb_cIOBuffer, &io_buffer_memory_view_entry);
}
diff --git a/lib/resolv.rb b/lib/resolv.rb
index 6b58f92813b435..03e5e9a75dfa72 100644
--- a/lib/resolv.rb
+++ b/lib/resolv.rb
@@ -562,7 +562,21 @@ def fetch_resource(name, typeclass)
next if !sender
senders[[candidate, requester, nameserver, port]] = sender
end
- reply, reply_name = requester.request(sender, tout)
+ begin
+ reply, reply_name = requester.request(sender, tout)
+ rescue ResolvTimeout
+ # Giving up part way through a frame loses stream sync, and a peer
+ # seen going away leaves the socket dead. Either way the requester
+ # says so, and the next attempt has to open a fresh connection. A
+ # timeout with the stream still on a frame boundary keeps it; a
+ # peer that leaves while nothing is being read goes unnoticed here
+ # and only shows up when the next request is written.
+ unless requester.reusable?
+ requesters.delete([nameserver, port])
+ requester.close
+ end
+ raise
+ end
case reply.rcode
when RCode::NoError
if reply.tc == 1 and not Requester::TCP === requester
@@ -698,6 +712,12 @@ def initialize
@socks = nil
end
+ # Whether another request may be sent over the same transport. Only a
+ # stream transport can end up in a state that rules this out.
+ def reusable?
+ true
+ end
+
def request(sender, tout)
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
timelimit = start + tout
@@ -724,7 +744,7 @@ def request(sender, tout)
raise ResolvTimeout
end
begin
- reply, from = recv_reply(select_result[0])
+ reply, from = recv_reply(select_result[0], timelimit)
rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD
Errno::ECONNRESET, # Windows
EOFError
@@ -801,7 +821,7 @@ def lazy_initialize
self
end
- def recv_reply(readable_socks)
+ def recv_reply(readable_socks, timelimit = nil)
lazy_initialize
reply, from = readable_socks[0].recvfrom(UDPSize)
return reply, [from[3],from[1]]
@@ -870,7 +890,7 @@ def lazy_initialize
self
end
- def recv_reply(readable_socks)
+ def recv_reply(readable_socks, timelimit = nil)
lazy_initialize
reply = readable_socks[0].recv(UDPSize)
return reply, nil
@@ -933,15 +953,28 @@ def initialize(host, port=Port)
sock = TCPSocket.new(@host, @port)
@socks = [sock]
@senders = {}
+ @reusable = true
+ end
+
+ def reusable?
+ @reusable
end
- def recv_reply(readable_socks)
- len_data = readable_socks[0].read(2)
+ def recv_reply(readable_socks, timelimit = nil)
+ sock = readable_socks[0]
+ len_data = read_exactly(sock, 2, timelimit)
raise EOFError if len_data.nil? || len_data.bytesize != 2
len = len_data.unpack('n')[0]
- reply = @socks[0].read(len)
+ reply = read_exactly(sock, len, timelimit)
raise EOFError if reply.nil? || reply.bytesize != len
+ @reusable = true
return reply, nil
+ rescue EOFError, SystemCallError
+ # Whatever the kernel reported, this socket cannot be trusted for
+ # another frame. In practice that is the peer closing or resetting;
+ # the rest is rare enough that erring towards reconnecting is right.
+ @reusable = false
+ raise
end
def sender(msg, data, host=@host, port=@port)
@@ -963,11 +996,40 @@ def send
end
def close
+ @reusable = false
super
@senders.each_key {|from,id|
DNS.free_request_id(@host, @port, id)
}
end
+
+ private
+
+ # Read +len+ bytes, giving up with ResolvTimeout once +timelimit+ (a
+ # CLOCK_MONOTONIC value) has passed. A shorter result means the peer
+ # closed the connection, which the caller turns into an EOFError.
+ # Consuming any byte marks the requester unusable until the whole frame
+ # has been read, since giving up in between loses frame sync. Without a
+ # +timelimit+ the read blocks instead and keeps no such mark; that is
+ # only for a caller still using the one argument form of #recv_reply.
+ def read_exactly(sock, len, timelimit)
+ return sock.read(len) unless timelimit
+ buf = String.new
+ while buf.bytesize < len
+ case chunk = sock.read_nonblock(len - buf.bytesize, exception: false)
+ when :wait_readable
+ remaining = timelimit - Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ raise ResolvTimeout if remaining <= 0
+ sock.wait_readable(remaining) or raise ResolvTimeout
+ when nil
+ break
+ else
+ @reusable = false
+ buf << chunk
+ end
+ end
+ buf
+ end
end
##
diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb
index 1b90b4f63ae706..10996374218c50 100644
--- a/lib/rubygems/commands/setup_command.rb
+++ b/lib/rubygems/commands/setup_command.rb
@@ -7,9 +7,6 @@
# RubyGems checkout or tarball.
class Gem::Commands::SetupCommand < Gem::Command
- HISTORY_HEADER = %r{^##\s*[\d.a-zA-Z]+\s*/\s*\d{4}-\d{2}-\d{2}\s*$}
- VERSION_MATCHER = %r{^##\s*([\d.a-zA-Z]+)\s*/\s*\d{4}-\d{2}-\d{2}\s*$}
-
ENV_PATHS = %w[/usr/bin/env /bin/env].freeze
def initialize
@@ -23,7 +20,7 @@ def initialize
add_option "--previous-version=VERSION",
"Previous version of RubyGems",
- "Used for changelog processing" do |version, options|
+ "Used for the release notes link" do |version, options|
options[:previous_version] = version
end
@@ -183,12 +180,6 @@ def execute
say
end
- if options[:previous_version].empty?
- options[:previous_version] = Gem::VERSION.sub(/[0-9]+$/, "0")
- end
-
- options[:previous_version] = Gem::Version.new(options[:previous_version])
-
show_release_notes
say
@@ -549,34 +540,16 @@ def remove_old_man_files(old_man_dir)
end
def show_release_notes
- release_notes = File.join Dir.pwd, "CHANGELOG.md"
-
- release_notes =
- if File.exist? release_notes
- history = File.read release_notes
-
- history.force_encoding Encoding::UTF_8
-
- text = history.split(HISTORY_HEADER)
- text.shift # correct an off-by-one generated by split
- version_lines = history.scan(HISTORY_HEADER)
- versions = history.scan(VERSION_MATCHER).flatten.map do |x|
- Gem::Version.new(x)
- end
+ ref = Gem::VERSION.include?(".dev") ? "master" : "v#{Gem::VERSION}"
+ link = "https://github.com/ruby/rubygems/blob/#{ref}/CHANGELOG.md"
- history_string = ""
-
- until versions.length == 0 ||
- versions.shift <= options[:previous_version] do
- history_string += version_lines.shift + text.shift
- end
-
- history_string
- else
- "Oh-no! Unable to find release notes!"
- end
-
- say release_notes
+ previous = options[:previous_version].to_s.strip
+ if previous.empty? || !Gem::Version.correct?(previous) ||
+ Gem::Version.new(previous) >= Gem::Version.new(Gem::VERSION)
+ say "See #{link} for the changes."
+ else
+ say "See #{link} for the changes since #{previous}."
+ end
end
def uninstall_old_gemcutter
diff --git a/lib/rubygems/update_suggestion.rb b/lib/rubygems/update_suggestion.rb
index 6f3ec5f4936747..563f569c2475db 100644
--- a/lib/rubygems/update_suggestion.rb
+++ b/lib/rubygems/update_suggestion.rb
@@ -10,10 +10,14 @@ module Gem::UpdateSuggestion
# Message to promote available RubyGems update with related gem update command.
def update_suggestion
+ current = Gem.rubygems_version
+ latest = Gem.latest_rubygems_version
+
<<-MESSAGE
-A new release of RubyGems is available: #{Gem.rubygems_version} → #{Gem.latest_rubygems_version}!
-Run `gem update --system #{Gem.latest_rubygems_version}` to update your installation.
+A new release of RubyGems is available: #{current} → #{latest}!
+See https://github.com/ruby/rubygems/blob/v#{latest}/CHANGELOG.md for the changes since #{current}.
+Run `gem update --system #{latest}` to update your installation.
MESSAGE
end
diff --git a/pathname_builtin.rb b/pathname_builtin.rb
index 1471350488409d..6b8108ce0871cc 100644
--- a/pathname_builtin.rb
+++ b/pathname_builtin.rb
@@ -2045,6 +2045,8 @@ def basename(...) self.class.new(File.basename(@path, ...)) end
# Pathname('nosuch').basename # => #
# ```
#
+ # Components are delimited by File::SEPARATOR and, if non-+nil+, File::ALT_SEPARATOR.
+
def dirname() self.class.new(File.dirname(@path)) end
# :markup: markdown
diff --git a/test/resolv/test_dns.rb b/test/resolv/test_dns.rb
index 0b81118c8c4a48..b4ef92b63845f9 100644
--- a/test/resolv/test_dns.rb
+++ b/test/resolv/test_dns.rb
@@ -843,6 +843,8 @@ def test_tcp_connection_closed_before_length
assert_raise(Resolv::ResolvTimeout) do
requester.request(sender, 2)
end
+ # The peer went away, so this connection cannot carry another request.
+ assert_equal(false, requester.reusable?)
ensure
requester.close
end
@@ -943,4 +945,314 @@ def test_tcp_connection_closed_with_partial_message_body
client_thread.join
end
end
+
+ def accept_within_timeout(t)
+ Timeout.timeout(EnvUtil.apply_timeout_scale(10)) { t.accept }
+ end
+
+ # Reads one length prefixed DNS message from +sock+.
+ def read_framed_query(sock)
+ len_data = sock.read(2)
+ flunk('the client closed the connection before sending a query') unless len_data&.bytesize == 2
+ Resolv::DNS::Message.decode(sock.read(len_data.unpack('n')[0]))
+ end
+
+ # Builds the encoded reply answering +query+ with a single A record.
+ def reply_for_query(query, address)
+ reply = Resolv::DNS::Message.new(query.id)
+ reply.qr = 1
+ reply.rd = query.rd
+ reply.ra = 1
+ query.each_question do |name, typeclass|
+ reply.add_question(name, typeclass)
+ reply.add_answer(name, 3600, Resolv::DNS::Resource::IN::A.new(address))
+ end
+ reply.encode
+ end
+
+ def framed(encoded)
+ [encoded.bytesize].pack('n') << encoded
+ end
+
+ # Runs a TCP server which replies with +reply+ and then keeps the connection
+ # open until the client side is done, so that only the timeout can end the
+ # request.
+ def with_tcp_server_keeping_connection_open(reply)
+ with_tcp('127.0.0.1', 0) do |t|
+ _, server_port, _, server_address = t.addr
+ done = Thread::Queue.new
+
+ server_thread = Thread.new do
+ ct = accept_within_timeout(t)
+ begin
+ ct.recv(512)
+ ct.write(reply)
+ done.pop
+ ensure
+ ct.close
+ end
+ end
+
+ client_thread = Thread.new do
+ begin
+ yield server_address, server_port
+ ensure
+ done.push(true)
+ end
+ end
+
+ assert_join_threads([client_thread, server_thread])
+ end
+ end
+
+ def request_over_tcp(server_address, server_port, tout)
+ requester = Resolv::DNS::Requester::TCP.new(server_address, server_port)
+ begin
+ msg = Resolv::DNS::Message.new
+ msg.add_question('example.org', Resolv::DNS::Resource::IN::A)
+ sender = requester.sender(msg, msg)
+ Timeout.timeout(EnvUtil.apply_timeout_scale(10)) do
+ requester.request(sender, EnvUtil.apply_timeout_scale(tout))
+ end
+ ensure
+ requester.close
+ end
+ end
+
+ def test_tcp_partial_length_prefix_kept_open
+ with_tcp_server_keeping_connection_open("\x00") do |server_address, server_port|
+ assert_raise(Resolv::ResolvTimeout) do
+ request_over_tcp(server_address, server_port, 0.5)
+ end
+ end
+ end
+
+ def test_tcp_partial_message_body_kept_open
+ reply = [10].pack('n') << '12345' # 5 bytes of a 10 byte message
+ with_tcp_server_keeping_connection_open(reply) do |server_address, server_port|
+ assert_raise(Resolv::ResolvTimeout) do
+ request_over_tcp(server_address, server_port, 0.5)
+ end
+ end
+ end
+
+ def test_tcp_complete_reply_kept_open
+ with_tcp('127.0.0.1', 0) do |t|
+ _, server_port, _, server_address = t.addr
+ done = Thread::Queue.new
+
+ server_thread = Thread.new do
+ ct = accept_within_timeout(t)
+ begin
+ ct.write(framed(reply_for_query(read_framed_query(ct), '192.0.2.1')))
+ done.pop
+ ensure
+ ct.close
+ end
+ end
+
+ client_thread = Thread.new do
+ begin
+ reply, = request_over_tcp(server_address, server_port, 2)
+ assert_equal(1, reply.answer.length)
+ assert_equal('192.0.2.1', reply.answer[0][2].address.to_s)
+ ensure
+ done.push(true)
+ end
+ end
+
+ assert_join_threads([client_thread, server_thread])
+ end
+ end
+
+ def test_tcp_reply_arriving_in_two_chunks
+ with_tcp('127.0.0.1', 0) do |t|
+ _, server_port, _, server_address = t.addr
+ done = Thread::Queue.new
+
+ server_thread = Thread.new do
+ ct = accept_within_timeout(t)
+ begin
+ reply = framed(reply_for_query(read_framed_query(ct), '192.0.2.1'))
+ ct.write(reply.byteslice(0, 6))
+ sleep EnvUtil.apply_timeout_scale(0.2)
+ ct.write(reply.byteslice(6..-1))
+ done.pop
+ ensure
+ ct.close
+ end
+ end
+
+ client_thread = Thread.new do
+ begin
+ reply, = request_over_tcp(server_address, server_port, 2)
+ assert_equal(1, reply.answer.length)
+ assert_equal('192.0.2.1', reply.answer[0][2].address.to_s)
+ ensure
+ done.push(true)
+ end
+ end
+
+ assert_join_threads([client_thread, server_thread])
+ end
+ end
+
+ def test_truncated_tcp_fallback_with_partial_message_body_kept_open
+ with_udp_and_tcp('127.0.0.1', 0) do |u, t|
+ _, server_port, _, server_address = u.addr
+ done = Thread::Queue.new
+
+ client_thread = Thread.new do
+ begin
+ dns = Resolv::DNS.new(nameserver_port: [[server_address, server_port]],
+ raise_timeout_errors: true)
+ begin
+ dns.timeouts = EnvUtil.apply_timeout_scale(0.5)
+ assert_raise(Resolv::ResolvError) do
+ Timeout.timeout(EnvUtil.apply_timeout_scale(10)) do
+ dns.getresources('foo.example.org', Resolv::DNS::Resource::IN::A)
+ end
+ end
+ ensure
+ dns.close
+ end
+ ensure
+ done.push(true)
+ end
+ end
+
+ udp_server_thread = Thread.new do
+ msg, (_, client_port, _, client_address) =
+ Timeout.timeout(EnvUtil.apply_timeout_scale(10)) { u.recvfrom(4096) }
+ id, word2, = msg.unpack('nnnnnn')
+ opcode = (word2 & 0x7800) >> 11
+ rd = (word2 & 0x0100) >> 8
+ qr = 1
+ tc = 1 # ask the client to retry over TCP
+ ra = 1
+ word2 = (qr << 15) | (opcode << 11) | (tc << 9) | (rd << 8) | (ra << 7)
+ u.send([id, word2, 0, 0, 0, 0].pack('nnnnnn'), 0, client_address, client_port)
+ end
+
+ tcp_server_thread = Thread.new do
+ ct = accept_within_timeout(t)
+ begin
+ ct.recv(512)
+ ct.write([10].pack('n') << '12345') # 5 bytes of a 10 byte message
+ done.pop
+ ensure
+ ct.close
+ end
+ end
+
+ assert_join_threads([client_thread, udp_server_thread, tcp_server_thread])
+ end
+ end
+
+ # A frame read that gives up part way through a frame leaves the stream
+ # between frame boundaries, so the retry has to start from a new connection.
+ def test_truncated_tcp_fallback_retries_on_a_new_connection
+ with_udp_and_tcp('127.0.0.1', 0) do |u, t|
+ _, server_port, _, server_address = u.addr
+
+ client_thread = Thread.new do
+ Resolv::DNS.open(nameserver_port: [[server_address, server_port]],
+ raise_timeout_errors: true) do |dns|
+ dns.timeouts = [EnvUtil.apply_timeout_scale(1),
+ EnvUtil.apply_timeout_scale(3)]
+ Timeout.timeout(EnvUtil.apply_timeout_scale(20)) do
+ dns.getresources('foo.example.org', Resolv::DNS::Resource::IN::A)
+ end
+ end
+ end
+
+ udp_server_thread = Thread.new do
+ msg, (_, client_port, _, client_address) =
+ Timeout.timeout(EnvUtil.apply_timeout_scale(10)) { u.recvfrom(4096) }
+ id, word2, = msg.unpack('nnnnnn')
+ opcode = (word2 & 0x7800) >> 11
+ rd = (word2 & 0x0100) >> 8
+ qr = 1
+ tc = 1 # ask the client to retry over TCP
+ ra = 1
+ word2 = (qr << 15) | (opcode << 11) | (tc << 9) | (rd << 8) | (ra << 7)
+ u.send([id, word2, 0, 0, 0, 0].pack('nnnnnn'), 0, client_address, client_port)
+ end
+
+ tcp_server_thread = Thread.new do
+ partial = accept_within_timeout(t)
+ begin
+ read_framed_query(partial)
+ partial.write([45].pack('n') << 'abcdef') # 6 bytes of a 45 byte message
+ complete = accept_within_timeout(t)
+ begin
+ complete.write(framed(reply_for_query(read_framed_query(complete), '192.0.2.1')))
+ ensure
+ complete.close
+ end
+ ensure
+ partial.close
+ end
+ end
+
+ result, = assert_join_threads([client_thread, udp_server_thread, tcp_server_thread])
+ assert_equal(['192.0.2.1'], result.map {|rr| rr.address.to_s })
+ end
+ end
+
+ # A timeout with no bytes read leaves the stream on a frame boundary, so the
+ # retry keeps the connection. The reply below only ever reaches the client if
+ # the second attempt reuses the socket the first one opened.
+ def test_truncated_tcp_fallback_keeps_the_connection_when_nothing_arrived
+ with_udp_and_tcp('127.0.0.1', 0) do |u, t|
+ _, server_port, _, server_address = u.addr
+ done = Thread::Queue.new
+
+ client_thread = Thread.new do
+ begin
+ Resolv::DNS.open(nameserver_port: [[server_address, server_port]],
+ raise_timeout_errors: true) do |dns|
+ dns.timeouts = [EnvUtil.apply_timeout_scale(0.5),
+ EnvUtil.apply_timeout_scale(5)]
+ Timeout.timeout(EnvUtil.apply_timeout_scale(20)) do
+ dns.getresources('foo.example.org', Resolv::DNS::Resource::IN::A)
+ end
+ end
+ ensure
+ done.push(true)
+ end
+ end
+
+ udp_server_thread = Thread.new do
+ msg, (_, client_port, _, client_address) =
+ Timeout.timeout(EnvUtil.apply_timeout_scale(10)) { u.recvfrom(4096) }
+ id, word2, = msg.unpack('nnnnnn')
+ opcode = (word2 & 0x7800) >> 11
+ rd = (word2 & 0x0100) >> 8
+ qr = 1
+ tc = 1 # ask the client to retry over TCP
+ ra = 1
+ word2 = (qr << 15) | (opcode << 11) | (tc << 9) | (rd << 8) | (ra << 7)
+ u.send([id, word2, 0, 0, 0, 0].pack('nnnnnn'), 0, client_address, client_port)
+ end
+
+ tcp_server_thread = Thread.new do
+ ct = accept_within_timeout(t)
+ begin
+ query = read_framed_query(ct)
+ # Stay silent past the first interval, then answer on this connection.
+ # It has to stay open until the client is done: the retry resends the
+ # query, and closing with that still unread would discard the reply.
+ sleep EnvUtil.apply_timeout_scale(1)
+ ct.write(framed(reply_for_query(query, '192.0.2.1')))
+ done.pop
+ ensure
+ ct.close
+ end
+ end
+
+ result, = assert_join_threads([client_thread, udp_server_thread, tcp_server_thread])
+ assert_equal(['192.0.2.1'], result.map {|rr| rr.address.to_s })
+ end
+ end
end
diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb
index 500ca5b7eb0b56..6e0c858fff52a6 100644
--- a/test/ruby/test_io_buffer.rb
+++ b/test/ruby/test_io_buffer.rb
@@ -4,6 +4,7 @@
require 'rbconfig/sizeof'
require 'io/nonblock'
require '-test-/io_buffer'
+require "-test-/memory_view"
class TestIOBuffer < Test::Unit::TestCase
experimental = Warning[:experimental]
@@ -1703,4 +1704,249 @@ def test_hexdump_width_zero
buffer.hexdump(0, 1, 0)
end
end
+
+ def test_memory_view_null
+ buffer = IO::Buffer.new(0)
+ assert_false(MemoryViewTestUtils.available?(buffer))
+ end
+
+ def test_memory_view_available
+ buffer = IO::Buffer.new(8)
+ assert_true(MemoryViewTestUtils.available?(buffer))
+ end
+
+ def test_memory_view_get_simple
+ data = +"\x00\x01\x02\x03"
+ IO::Buffer.for(data) do |buffer|
+ info = MemoryViewTestUtils.get_memory_view_info(buffer)
+ assert_equal({
+ obj: buffer,
+ byte_size: data.bytesize,
+ readonly: true,
+ format: nil,
+ item_size: 1,
+ ndim: 1,
+ shape: nil,
+ strides: nil,
+ sub_offsets: nil,
+ },
+ info)
+ end
+ end
+
+ def test_memory_view_get_writable
+ data = +"\x00\x01\x02\x03"
+ IO::Buffer.for(data) do |buffer|
+ flags = MemoryViewTestUtils::WRITABLE
+ info = MemoryViewTestUtils.get_memory_view_info(buffer, flags)
+ assert_equal({
+ obj: buffer,
+ byte_size: data.bytesize,
+ readonly: false,
+ format: nil,
+ item_size: 1,
+ ndim: 1,
+ shape: nil,
+ strides: nil,
+ sub_offsets: nil,
+ },
+ info)
+ end
+ end
+
+ def test_memory_view_get_format
+ data = "\x00\x01\x02\x03".freeze
+ IO::Buffer.for(data) do |buffer|
+ flags = MemoryViewTestUtils::FORMAT
+ info = MemoryViewTestUtils.get_memory_view_info(buffer, flags)
+ assert_equal({
+ obj: buffer,
+ byte_size: data.bytesize,
+ readonly: true,
+ format: "C",
+ item_size: 1,
+ ndim: 1,
+ shape: nil,
+ strides: nil,
+ sub_offsets: nil,
+ },
+ info)
+ end
+ end
+
+ def test_memory_view_get_multi_dimensional
+ data = "\x00\x01\x02\x03".freeze
+ IO::Buffer.for(data) do |buffer|
+ flags = MemoryViewTestUtils::MULTI_DIMENSIONAL
+ info = MemoryViewTestUtils.get_memory_view_info(buffer, flags)
+ assert_equal({
+ obj: buffer,
+ byte_size: data.bytesize,
+ readonly: true,
+ format: nil,
+ item_size: 1,
+ ndim: 1,
+ shape: [data.bytesize],
+ strides: nil,
+ sub_offsets: nil,
+ },
+ info)
+ end
+ end
+
+ def test_memory_view_get_strides
+ data = "\x00\x01\x02\x03".freeze
+ IO::Buffer.for(data) do |buffer|
+ flags = MemoryViewTestUtils::STRIDES
+ info = MemoryViewTestUtils.get_memory_view_info(buffer, flags)
+ assert_equal({
+ obj: buffer,
+ byte_size: data.bytesize,
+ readonly: true,
+ format: nil,
+ item_size: 1,
+ ndim: 1,
+ shape: [data.bytesize],
+ strides: [1],
+ sub_offsets: nil,
+ },
+ info)
+ end
+ end
+
+ def test_memory_view_get_row_major
+ data = "\x00\x01\x02\x03".freeze
+ IO::Buffer.for(data) do |buffer|
+ flags = MemoryViewTestUtils::ROW_MAJOR
+ info = MemoryViewTestUtils.get_memory_view_info(buffer, flags)
+ assert_equal({
+ obj: buffer,
+ byte_size: data.bytesize,
+ readonly: true,
+ format: nil,
+ item_size: 1,
+ ndim: 1,
+ shape: [data.bytesize],
+ strides: [1],
+ sub_offsets: nil,
+ },
+ info)
+ end
+ end
+
+ def test_memory_view_get_column_major
+ data = "\x00\x01\x02\x03".freeze
+ IO::Buffer.for(data) do |buffer|
+ flags = MemoryViewTestUtils::COLUMN_MAJOR
+ info = MemoryViewTestUtils.get_memory_view_info(buffer, flags)
+ assert_equal({
+ obj: buffer,
+ byte_size: data.bytesize,
+ readonly: true,
+ format: nil,
+ item_size: 1,
+ ndim: 1,
+ shape: [data.bytesize],
+ strides: [1],
+ sub_offsets: nil,
+ },
+ info)
+ end
+ end
+
+ def test_memory_view_get_any_contiguous
+ data = "\x00\x01\x02\x03".freeze
+ IO::Buffer.for(data) do |buffer|
+ flags = MemoryViewTestUtils::ANY_CONTIGUOUS
+ info = MemoryViewTestUtils.get_memory_view_info(buffer, flags)
+ assert_equal({
+ obj: buffer,
+ byte_size: data.bytesize,
+ readonly: true,
+ format: nil,
+ item_size: 1,
+ ndim: 1,
+ shape: [data.bytesize],
+ strides: [1],
+ sub_offsets: nil,
+ },
+ info)
+ end
+ end
+
+ def test_memory_view_get_indirect
+ data = "\x00\x01\x02\x03".freeze
+ IO::Buffer.for(data) do |buffer|
+ flags = MemoryViewTestUtils::INDIRECT
+ info = MemoryViewTestUtils.get_memory_view_info(buffer, flags)
+ assert_equal({
+ obj: buffer,
+ byte_size: data.bytesize,
+ readonly: true,
+ format: nil,
+ item_size: 1,
+ ndim: 1,
+ shape: [data.bytesize],
+ strides: [1],
+ sub_offsets: nil,
+ },
+ info)
+ end
+ end
+
+ def test_memory_view_readonly
+ data = "\x00\x01\x02\x03".freeze
+ buffer = IO::Buffer.for(data)
+ # rb_memory_view_get(RUBY_MEMORY_VIEW_WRITABLE) is failed with
+ # readonly IO::Buffer.
+ assert_nil(MemoryViewTestUtils.set_data(buffer, 1, 0x11))
+ assert_equal(data, MemoryViewTestUtils.get_data(buffer, 0..(data.bytesize)))
+ end
+
+ def test_memory_view_writable
+ IO::Buffer.for(+"\x00\x01\x02\x03") do |buffer|
+ assert_true(MemoryViewTestUtils.set_data(buffer, 1, 0x11))
+ assert_equal(0x11, buffer.get_value(:U8, 1))
+ end
+ end
+
+ def test_memory_view_locked
+ IO::Buffer.for("\x00\x01\x02\x03".freeze) do |buffer|
+ flags = MemoryViewTestUtils::SIMPLE
+ MemoryViewTestUtils.get(buffer, flags) do
+ assert_predicate buffer, :locked?
+ end
+ refute_predicate buffer, :locked?
+ end
+ end
+
+ def test_memory_view_nested_locked
+ IO::Buffer.for("\x00\x01\x02\x03".freeze) do |buffer|
+ flags = MemoryViewTestUtils::SIMPLE
+ MemoryViewTestUtils.get(buffer, flags) do
+ MemoryViewTestUtils.get(buffer, flags) do
+ assert_predicate buffer, :locked?
+ end
+ assert_predicate buffer, :locked?
+ end
+ refute_predicate buffer, :locked?
+ end
+ end
+
+ def test_memory_view_sliced_nested_locked
+ IO::Buffer.for("\x00\x01\x02\x03".freeze) do |buffer|
+ sliced = buffer.slice(1, 2)
+ flags = MemoryViewTestUtils::SIMPLE
+ MemoryViewTestUtils.get(sliced, flags) do
+ MemoryViewTestUtils.get(sliced, flags) do
+ assert_predicate sliced, :locked?
+ assert_predicate buffer, :locked?
+ end
+ assert_predicate sliced, :locked?
+ assert_predicate buffer, :locked?
+ end
+ refute_predicate sliced, :locked?
+ refute_predicate buffer, :locked?
+ end
+ end
end
diff --git a/test/rubygems/test_gem_commands_setup_command.rb b/test/rubygems/test_gem_commands_setup_command.rb
index ba410d64e635cd..484a802fe536e4 100644
--- a/test/rubygems/test_gem_commands_setup_command.rb
+++ b/test/rubygems/test_gem_commands_setup_command.rb
@@ -407,56 +407,129 @@ def test_remove_old_man_files
end
def test_show_release_notes
- @default_external = @ui.outs.external_encoding
- @ui.outs.set_encoding Encoding::US_ASCII
+ @cmd.options[:previous_version] = "2.0.2"
- @cmd.options[:previous_version] = Gem::Version.new "2.0.2"
+ with_gem_version "4.0.19" do
+ use_ui @ui do
+ @cmd.show_release_notes
+ end
+ end
- File.open "CHANGELOG.md", "w" do |io|
- io.puts <<-HISTORY_TXT
-# Changelog
+ expected = "See https://github.com/ruby/rubygems/blob/v4.0.19/CHANGELOG.md for the changes since 2.0.2.\n"
-## #{Gem::VERSION} / 2013-03-26
+ assert_equal expected, @ui.output
+ end
-### Bug fixes:
- * Fixed release note display for LANG=C when installing rubygems
- * π is tasty
+ def test_show_release_notes_dev_version
+ @cmd.options[:previous_version] = "2.0.2"
-## 2.0.2 / 2013-03-06
+ with_gem_version "4.1.0.dev" do
+ use_ui @ui do
+ @cmd.show_release_notes
+ end
+ end
-### Bug fixes:
- * Other bugs fixed
+ expected = "See https://github.com/ruby/rubygems/blob/master/CHANGELOG.md for the changes since 2.0.2.\n"
-## 2.0.1 / 2013-03-05
+ assert_equal expected, @ui.output
+ end
-### Bug fixes:
- * Yet more bugs fixed
- HISTORY_TXT
+ def test_show_release_notes_released_prerelease
+ @cmd.options[:previous_version] = "2.0.2"
+
+ with_gem_version "4.1.0.beta1" do
+ use_ui @ui do
+ @cmd.show_release_notes
+ end
end
- use_ui @ui do
- @cmd.show_release_notes
+ expected = "See https://github.com/ruby/rubygems/blob/v4.1.0.beta1/CHANGELOG.md for the changes since 2.0.2.\n"
+
+ assert_equal expected, @ui.output
+ end
+
+ def test_show_release_notes_downgrade
+ @cmd.handle_options ["--previous-version", "4.0.19"]
+
+ with_gem_version "4.0.10" do
+ use_ui @ui do
+ @cmd.show_release_notes
+ end
end
- expected = <<-EXPECTED
-## #{Gem::VERSION} / 2013-03-26
+ expected = "See https://github.com/ruby/rubygems/blob/v4.0.10/CHANGELOG.md for the changes.\n"
-### Bug fixes:
- * Fixed release note display for LANG=C when installing rubygems
- * π is tasty
+ assert_equal expected, @ui.output
+ end
- EXPECTED
+ def test_show_release_notes_same_version
+ @cmd.handle_options ["--previous-version", "4.0.19"]
- output = @ui.output
- output.force_encoding Encoding::UTF_8
+ with_gem_version "4.0.19" do
+ use_ui @ui do
+ @cmd.show_release_notes
+ end
+ end
- assert_equal expected, output
- ensure
- @ui.outs.set_encoding @default_external if @default_external
+ expected = "See https://github.com/ruby/rubygems/blob/v4.0.19/CHANGELOG.md for the changes.\n"
+
+ assert_equal expected, @ui.output
+ end
+
+ def test_show_release_notes_whitespace_previous_version
+ @cmd.handle_options ["--previous-version", " 2.0.2\r"]
+
+ with_gem_version "4.0.19" do
+ use_ui @ui do
+ @cmd.show_release_notes
+ end
+ end
+
+ expected = "See https://github.com/ruby/rubygems/blob/v4.0.19/CHANGELOG.md for the changes since 2.0.2.\n"
+
+ assert_equal expected, @ui.output
+ end
+
+ def test_show_release_notes_invalid_previous_version
+ @cmd.handle_options ["--previous-version", "\e[31mnot-a-version"]
+
+ with_gem_version "4.0.19" do
+ use_ui @ui do
+ @cmd.show_release_notes
+ end
+ end
+
+ expected = "See https://github.com/ruby/rubygems/blob/v4.0.19/CHANGELOG.md for the changes.\n"
+
+ assert_equal expected, @ui.output
+ end
+
+ def test_show_release_notes_without_previous_version
+ @cmd.options[:previous_version] = ""
+
+ with_gem_version "4.0.19" do
+ use_ui @ui do
+ @cmd.show_release_notes
+ end
+ end
+
+ expected = "See https://github.com/ruby/rubygems/blob/v4.0.19/CHANGELOG.md for the changes.\n"
+
+ assert_equal expected, @ui.output
end
private
+ def with_gem_version(version)
+ original = Gem::VERSION
+ Gem.send :remove_const, :VERSION
+ Gem.const_set :VERSION, version
+ yield
+ ensure
+ Gem.send :remove_const, :VERSION
+ Gem.const_set :VERSION, original
+ end
+
def create_dummy_files(list)
list.each do |file|
FileUtils.mkdir_p File.dirname(file)
diff --git a/test/rubygems/test_gem_update_suggestion.rb b/test/rubygems/test_gem_update_suggestion.rb
index 8cb8ee57ff90be..dc5395f8344b79 100644
--- a/test/rubygems/test_gem_update_suggestion.rb
+++ b/test/rubygems/test_gem_update_suggestion.rb
@@ -55,9 +55,10 @@ def self.with_eligible_environment(
def test_update_suggestion
Gem.stub :rubygems_version, Gem::Version.new("1.2.3") do
Gem.stub :latest_rubygems_version, Gem::Version.new("2.0.0") do
- assert_equal @cmd.update_suggestion, <<~SUGGESTION
+ assert_equal <<~SUGGESTION, @cmd.update_suggestion
A new release of RubyGems is available: 1.2.3 → 2.0.0!
+ See https://github.com/ruby/rubygems/blob/v2.0.0/CHANGELOG.md for the changes since 1.2.3.
Run `gem update --system 2.0.0` to update your installation.
SUGGESTION