From a5b64c82742b5322c97bbfbe42793175ed799766 Mon Sep 17 00:00:00 2001 From: git Date: Fri, 4 Sep 2026 06:58:16 +0000 Subject: [PATCH 01/14] Update bundled gems list as of 2026-09-04 --- NEWS.md | 2 +- gems/bundled_gems | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 658b98bbb1dcab..b626d697e3efcf 100644 --- a/NEWS.md +++ b/NEWS.md @@ -226,7 +226,7 @@ They are still available on rubygems.org and can be installed with * 0.6.2 to [v0.6.3][net-imap-v0.6.3], [v0.6.4][net-imap-v0.6.4], [v0.6.4.1][net-imap-v0.6.4.1], [v0.6.5][net-imap-v0.6.5], [v0.6.6][net-imap-v0.6.6] * rbs 4.2.0 * 3.10.0 to [v3.10.1][rbs-v3.10.1], [v3.10.2][rbs-v3.10.2], [v3.10.3][rbs-v3.10.3], [v3.10.4][rbs-v3.10.4], [v4.0.0.dev.1][rbs-v4.0.0.dev.1], [v4.0.0.dev.2][rbs-v4.0.0.dev.2], [v4.0.0.dev.3][rbs-v4.0.0.dev.3], [v4.0.0.dev.4][rbs-v4.0.0.dev.4], [v4.0.0.dev.5][rbs-v4.0.0.dev.5], [v4.0.0][rbs-v4.0.0], [v4.0.1.dev.1][rbs-v4.0.1.dev.1], [v4.0.1.dev.2][rbs-v4.0.1.dev.2], [v4.0.1][rbs-v4.0.1], [v4.0.2][rbs-v4.0.2], [v4.0.3][rbs-v4.0.3], [v4.1.0.pre.1][rbs-v4.1.0.pre.1], [v4.1.0.pre.2][rbs-v4.1.0.pre.2], [v4.1.0][rbs-v4.1.0], [v4.1.1.pre.1][rbs-v4.1.1.pre.1], [v4.1.1][rbs-v4.1.1], [v4.1.2][rbs-v4.1.2], [v4.1.3][rbs-v4.1.3], [v4.2.0.pre.1][rbs-v4.2.0.pre.1], [v4.2.0][rbs-v4.2.0] -* typeprof 0.32.0 +* typeprof 0.33.0 * mutex_m 0.3.0 * bigdecimal 4.1.2 * 4.0.1 to [v4.1.0][bigdecimal-v4.1.0], [v4.1.1][bigdecimal-v4.1.1], [v4.1.2][bigdecimal-v4.1.2] diff --git a/gems/bundled_gems b/gems/bundled_gems index ead76f59bcae20..1f6ecf9d426ea0 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -17,7 +17,7 @@ net-smtp 0.5.1 https://github.com/ruby/net-smtp matrix 0.4.3 https://github.com/ruby/matrix prime 0.1.4 https://github.com/ruby/prime rbs 4.2.0 https://github.com/ruby/rbs -typeprof 0.32.0 https://github.com/ruby/typeprof b950f910c6b6e4736988637f46a1ae256384c9f7 +typeprof 0.33.0 https://github.com/ruby/typeprof debug 1.11.1 https://github.com/ruby/debug 6510cfbc7496c55ebbefa437a25c17ca58f7c5eb racc 1.8.1 https://github.com/ruby/racc mutex_m 0.3.0 https://github.com/ruby/mutex_m From 5f9c9011fa9ba976985d307df5c81ca14d430db5 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 4 Sep 2026 16:35:35 +0900 Subject: [PATCH 02/14] [ruby/rubygems] Stop depending on `getconf` in env helper specs Windows has no `getconf`, so these examples raise Errno::ENOENT on mswin unless MSYS2 or Git for Windows happens to be on PATH. Keep the current PATH as the base there, which also preserves the directories the Ruby under test loads its DLLs from. https://github.com/ruby/rubygems/commit/8963ddc378 Co-Authored-By: Claude Opus 5 --- spec/bundler/runtime/env_helpers_spec.rb | 4 ++-- spec/bundler/support/helpers.rb | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/spec/bundler/runtime/env_helpers_spec.rb b/spec/bundler/runtime/env_helpers_spec.rb index c4ebdd1fd28998..38f501e7cde6aa 100644 --- a/spec/bundler/runtime/env_helpers_spec.rb +++ b/spec/bundler/runtime/env_helpers_spec.rb @@ -21,7 +21,7 @@ def run_bundler_script(env, script) create_file("source.rb", <<-RUBY) print Bundler.original_env["PATH"] RUBY - path = `getconf PATH`.strip + "#{File::PATH_SEPARATOR}/foo" + path = default_system_path + "#{File::PATH_SEPARATOR}/foo" with_path_as(path) do bundle_exec_ruby(bundled_app("source.rb").to_s) expect(stdboth).to eq(path) @@ -49,7 +49,7 @@ def run_bundler_script(env, script) end exec(Gem.ruby, __FILE__, (count - 1).to_s) RUBY - path = `getconf PATH`.strip + File::PATH_SEPARATOR + File.dirname(Gem.ruby) + path = default_system_path + File::PATH_SEPARATOR + File.dirname(Gem.ruby) with_path_as(path) do build_bundler_context bundle_exec_ruby("#{bundled_app("exe.rb")} 2") diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb index 8798cca57bb546..7925663dd1d128 100644 --- a/spec/bundler/support/helpers.rb +++ b/spec/bundler/support/helpers.rb @@ -428,6 +428,12 @@ def with_gem_path_as(path) end end + # Windows has no `getconf`, and its current PATH holds the directories the + # Ruby under test finds its DLLs in, so keep it as is there. + def default_system_path + Gem.win_platform? ? ENV["PATH"] : `getconf PATH`.strip + end + def with_path_as(path) without_env_side_effects do ENV["PATH"] = path.to_s From d643419b73dc56052249985128bac7bb2dd4e2e6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 4 Sep 2026 18:41:20 +0900 Subject: [PATCH 03/14] Test `FMODE_SYNC` in `rb_io_open_descriptor` Ensure alternative implementations preserve synchronized mode when opening an IO from a descriptor. --- spec/ruby/optional/capi/ext/io_spec.c | 1 + spec/ruby/optional/capi/io_spec.rb | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/spec/ruby/optional/capi/ext/io_spec.c b/spec/ruby/optional/capi/ext/io_spec.c index 5cf96ecc0eb2d1..7aaceef0c06797 100644 --- a/spec/ruby/optional/capi/ext/io_spec.c +++ b/spec/ruby/optional/capi/ext/io_spec.c @@ -416,6 +416,7 @@ void Init_io_spec(void) { rb_define_method(cls, "rb_eIOTimeoutError", io_spec_rb_eIOTimeoutError, 0); rb_define_const(cls, "FMODE_READABLE", INT2FIX(FMODE_READABLE)); rb_define_const(cls, "FMODE_WRITABLE", INT2FIX(FMODE_WRITABLE)); + rb_define_const(cls, "FMODE_SYNC", INT2FIX(FMODE_SYNC)); rb_define_const(cls, "FMODE_BINMODE", INT2FIX(FMODE_BINMODE)); rb_define_const(cls, "FMODE_TEXTMODE", INT2FIX(FMODE_TEXTMODE)); rb_define_const(cls, "ECONV_UNIVERSAL_NEWLINE_DECORATOR", INT2FIX(ECONV_UNIVERSAL_NEWLINE_DECORATOR)); diff --git a/spec/ruby/optional/capi/io_spec.rb b/spec/ruby/optional/capi/io_spec.rb index ce8ba6cb5da155..6db95611cd3a9c 100644 --- a/spec/ruby/optional/capi/io_spec.rb +++ b/spec/ruby/optional/capi/io_spec.rb @@ -726,6 +726,14 @@ io.should_not.binmode? end + it "sets sync mode" do + mode = CApiIOSpecs::FMODE_READABLE | CApiIOSpecs::FMODE_SYNC + io = @o.rb_io_open_descriptor(File, @r_io.fileno, mode, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + + io.should.sync + @o.rb_io_mode_sync_flag(io).should == true + end + it "sets the specified timeout" do io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) io.timeout.should == 60 From 4eaa91451d9394c5dbd5ecbb6f5c678b1820a376 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 4 Sep 2026 18:49:36 +0900 Subject: [PATCH 04/14] Check for if the `id` command succeeded as well Remove dependency on `which` command. --- spec/ruby/library/etc/getlogin_spec.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/spec/ruby/library/etc/getlogin_spec.rb b/spec/ruby/library/etc/getlogin_spec.rb index d6211909c8ef3f..bf6dda931a15ed 100644 --- a/spec/ruby/library/etc/getlogin_spec.rb +++ b/spec/ruby/library/etc/getlogin_spec.rb @@ -29,11 +29,12 @@ else # Etc.getlogin returns the same result of logname(2) # if it returns non NULL - if system("which logname", out: File::NULL, err: File::NULL) - Etc.getlogin.should == `logname`.chomp - else - # fallback to `id` command since `logname` is not available - Etc.getlogin.should == `id -un`.chomp + logname = [%w[logname], %w[id -un]].find do |cmd| + name = IO.popen(cmd, err: File::NULL, &:read) rescue next + break name.chomp if $?.success? + end + if logname + Etc.getlogin.should == logname end end else From 84b0ec712e39a336d23f8dfb5b929f2bb54fa736 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 2 Sep 2026 14:42:21 +0900 Subject: [PATCH 05/14] [ruby/io-console] Fix the main target https://github.com/ruby/io-console/commit/207a809235 --- ext/io/console/extconf.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/io/console/extconf.rb b/ext/io/console/extconf.rb index fb058f619d7292..65d3605762f530 100644 --- a/ext/io/console/extconf.rb +++ b/ext/io/console/extconf.rb @@ -54,10 +54,10 @@ have_func("rb_prepend_module") # not exported by TruffleRuby vk_tool = find_executable("gperf") create_makefile("io/console") {|conf| + conf << "###\n" "all: # the default target\n" if vk_header conf << <<~MK VK_HEADER = #{vk_header} - all: console.#$OBJEXT: $(VK_HEADER) MK end From 3e265cc0898d254b50b1264672ed03a0bcaa7202 Mon Sep 17 00:00:00 2001 From: Oskar Eichler Date: Sun, 30 Aug 2026 00:06:29 +0200 Subject: [PATCH 06/14] [ruby/io-console] Use ttyname_r return codes for errors https://github.com/ruby/io-console/commit/1cd1ed22af --- ext/io/console/console.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/io/console/console.c b/ext/io/console/console.c index ac97dc32feb715..7ca48d53b85d56 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -2238,17 +2238,17 @@ console_ttyname(VALUE io) char termname[1024], *tn = termname; size_t size = sizeof(termname); int e; - if (ttyname_r(fd, tn, size) == 0) + if ((e = ttyname_r(fd, tn, size)) == 0) return rb_interned_str_cstr(tn); - if ((e = errno) == ERANGE) { + if (e == ERANGE) { VALUE s = rb_str_new(0, size); while (1) { tn = RSTRING_PTR(s); size = rb_str_capacity(s); - if (ttyname_r(fd, tn, size) == 0) { + if ((e = ttyname_r(fd, tn, size)) == 0) { return rb_str_to_interned_str(rb_str_resize(s, strlen(tn))); } - if ((e = errno) != ERANGE) break; + if (e != ERANGE) break; if ((size *= 2) >= INT_MAX/2) break; rb_str_resize(s, size); } From c6f517a17246bedf428cda0ab7feb72fc86e1ad3 Mon Sep 17 00:00:00 2001 From: Oskar Eichler <62393985+OskarEichler@users.noreply.github.com> Date: Wed, 2 Sep 2026 09:47:58 +0300 Subject: [PATCH 07/14] [ruby/io-console] [DOC] Correct ttyname documentation (https://github.com/ruby/io-console/pull/154) https://github.com/ruby/io-console/commit/7ad5f44a1d --- ext/io/console/console.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/io/console/console.c b/ext/io/console/console.c index 7ca48d53b85d56..04d0f7007c56f1 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -2223,7 +2223,7 @@ io_getpass(int argc, VALUE *argv, VALUE io) * call-seq: * io.ttyname -> string or nil * - * Returns name of associated terminal (tty) if +io+ is not a tty. + * Returns name of associated terminal (tty) if +io+ is a tty. * Returns +nil+ otherwise. */ static VALUE From 2d281a898d8dd72f70b7f76bc0e502f8e118f196 Mon Sep 17 00:00:00 2001 From: Oskar Eichler Date: Sun, 30 Aug 2026 00:07:31 +0200 Subject: [PATCH 08/14] [ruby/io-console] Preserve nil from JRuby getpass at EOF https://github.com/ruby/io-console/commit/4fed496030 --- test/io/console/test_io_console.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/io/console/test_io_console.rb b/test/io/console/test_io_console.rb index 48743fc296600c..a224de2123d7fd 100644 --- a/test/io/console/test_io_console.rb +++ b/test/io/console/test_io_console.rb @@ -367,6 +367,17 @@ def test_getpass end end + def test_getpass_empty + helper do |m, s| + result = Thread.new {s.getpass("> ")} + assert_equal("> ", m.readpartial(10)) + Timeout.timeout(1) {Thread.pass while s.echo?} + m.write "\C-D" + assert_nil(result.value) + assert_equal("\r\n", m.readpartial(10)) + end + end + def test_iflush pend "stty cannot flush terminal queues" if IO.private_method_defined?(:_io_console_stty) From 9f8e0e106d5af5abdf8c42ec4820698fc8ca45a0 Mon Sep 17 00:00:00 2001 From: Oskar Eichler Date: Sun, 30 Aug 2026 00:07:47 +0200 Subject: [PATCH 09/14] [ruby/io-console] Return nil when JRuby has no console device https://github.com/ruby/io-console/commit/26b35cafcb --- test/io/console/test_io_console.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/io/console/test_io_console.rb b/test/io/console/test_io_console.rb index a224de2123d7fd..f59ecd065a109f 100644 --- a/test/io/console/test_io_console.rb +++ b/test/io/console/test_io_console.rb @@ -731,7 +731,7 @@ def run_noctty(src) end def test_noctty - assert_equal(["nil"], run_noctty("IO.console")) + assert_equal(["[nil, nil]"], run_noctty("[IO.console, IO.console(:tty?)]")) if IO.method_defined?(:ttyname) assert_equal(["nil"], run_noctty("STDIN.ttyname rescue $!")) end From 6d2cfeea4a1f15bc52b45f4a60e06a661becae8f Mon Sep 17 00:00:00 2001 From: Oskar Eichler Date: Sun, 30 Aug 2026 00:08:03 +0200 Subject: [PATCH 10/14] [ruby/io-console] Reject incomplete JRuby cursor responses https://github.com/ruby/io-console/commit/27fde7fdd9 --- test/io/console/test_io_console.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/io/console/test_io_console.rb b/test/io/console/test_io_console.rb index f59ecd065a109f..179d322e4d3eb5 100644 --- a/test/io/console/test_io_console.rb +++ b/test/io/console/test_io_console.rb @@ -467,6 +467,8 @@ def test_cursor_position con.cursor_right(4); con.puts con.cursor_left(2); con.puts con.cursor_up(1); con.puts + p con.cursor + p con.cursor end; assert_equal("\e[6n", r.readpartial(5)) w.print("\e[12;34R"); w.flush @@ -475,6 +477,14 @@ def test_cursor_position assert_equal("\e[4C", r.gets.chomp) assert_equal("\e[2D", r.gets.chomp) assert_equal("\e[1A", r.gets.chomp) + + assert_equal("\e[6n", r.readpartial(5)) + w.print("\e[12R"); w.flush + assert_equal("nil", r.gets.chomp) + + assert_equal("\e[6n", r.readpartial(5)) + w.print("\e[12;34;56R"); w.flush + assert_equal("nil", r.gets.chomp) end end From f7a08bbd7e46be56c97e8d53ef88b2e0be9c3f35 Mon Sep 17 00:00:00 2001 From: Oskar Eichler Date: Sun, 30 Aug 2026 00:08:19 +0200 Subject: [PATCH 11/14] [ruby/io-console] Ignore nonpositive console size overrides https://github.com/ruby/io-console/commit/8c26435cda --- ext/io/console/lib/console/size.rb | 6 ++++-- test/io/console/test_io_console.rb | 24 ++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/ext/io/console/lib/console/size.rb b/ext/io/console/lib/console/size.rb index 14b9a74b224dfa..5a3ed0a0e3046e 100644 --- a/ext/io/console/lib/console/size.rb +++ b/ext/io/console/lib/console/size.rb @@ -1,9 +1,11 @@ # frozen_string_literal: false # fallback to console window size def IO.default_console_size + lines = ENV["LINES"].to_i + columns = ENV["COLUMNS"].to_i [ - ENV["LINES"].to_i.nonzero? || 25, - ENV["COLUMNS"].to_i.nonzero? || 80, + lines.positive? ? lines : 25, + columns.positive? ? columns : 80, ] end diff --git a/test/io/console/test_io_console.rb b/test/io/console/test_io_console.rb index 179d322e4d3eb5..243265c8943b2b 100644 --- a/test/io/console/test_io_console.rb +++ b/test/io/console/test_io_console.rb @@ -712,7 +712,7 @@ def test_ttyname if noctty require 'tempfile' NOCTTY = noctty - def run_noctty(src) + def run_noctty(src, require: "io/console", env: nil) t = Tempfile.new("noctty_out") t.close t2 = Tempfile.new("noctty_run") @@ -722,12 +722,13 @@ def run_noctty(src) '-e', 'open(ARGV[0], "w") {|f|', '-e', 'STDOUT.reopen(f)', '-e', 'STDERR.reopen(f)', - '-e', 'require "io/console"', + '-e', "require #{require.dump}", '-e', "f.puts (#{src}).inspect", '-e', 'f.flush', '-e', 'File.unlink(ARGV[1])', '-e', '}', '--', t.path, t2.path] + cmd.unshift(env) if env assert_ruby_status(cmd, rubybin: NOCTTY[0]) 30.times do break unless File.exist?(t2.path) @@ -746,6 +747,25 @@ def test_noctty assert_equal(["nil"], run_noctty("STDIN.ttyname rescue $!")) end end + + def test_default_console_size + [ + [40, 100], + [nil, 50], + [30, nil], + [0, 50], + [30, 0], + [-1, 50], + [30, -1], + ].each do |lines, columns| + result = run_noctty("IO.console_size", + require: "io/console/size", + env: {"LINES"=>lines&.to_s, "COLUMNS"=>columns&.to_s}) + lines = 25 unless lines&.positive? + columns = 80 unless columns&.positive? + assert_equal([[lines, columns].inspect], result) + end + end end end From 1fffdc15e6dad3a21d3f29cbd487449041740816 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 4 Sep 2026 11:15:19 +0900 Subject: [PATCH 12/14] [ruby/io-console] Propagate the path of `io/console/size` https://github.com/ruby/io-console/commit/9b006071c3 --- test/io/console/test_io_console.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/io/console/test_io_console.rb b/test/io/console/test_io_console.rb index 243265c8943b2b..ffc5b5008ed90f 100644 --- a/test/io/console/test_io_console.rb +++ b/test/io/console/test_io_console.rb @@ -1,6 +1,7 @@ # frozen_string_literal: false begin require 'io/console' + require 'io/console/size' require 'test/unit' require 'pty' rescue LoadError From 28aa571402a945ce051d08deb2a2bdc2456e5674 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 4 Sep 2026 00:15:26 +0900 Subject: [PATCH 13/14] [ruby/io-console] Fix `getpass` to use `\n` explicitly regardless the RS https://github.com/ruby/io-console/commit/976eb3c7d4 --- ext/io/console/console.c | 12 +++++++++--- test/io/console/test_io_console.rb | 15 ++++++++++----- test/io/console/test_ractor.rb | 2 +- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/ext/io/console/console.c b/ext/io/console/console.c index 04d0f7007c56f1..eb1c876e9b91fe 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -2135,10 +2135,17 @@ puts_call(VALUE io) return rb_io_write(io, rb_default_rs); } +static VALUE +funcall_with_rs(VALUE obj, ID mid) +{ + const VALUE rs = rb_default_rs; /* rvalue in TruffleRuby */ + return rb_funcallv(obj, mid, 1, &rs); +} + static VALUE gets_call(VALUE io) { - return rb_funcallv(io, id_gets, 0, 0); + return funcall_with_rs(io, id_gets); } static VALUE @@ -2161,8 +2168,7 @@ static VALUE str_chomp(VALUE str) { if (!NIL_P(str)) { - const VALUE rs = rb_default_rs; /* rvalue in TruffleRuby */ - rb_funcallv(str, id_chomp_bang, 1, &rs); + funcall_with_rs(str, id_chomp_bang); } return str; } diff --git a/test/io/console/test_io_console.rb b/test/io/console/test_io_console.rb index ffc5b5008ed90f..af207c820a5a5e 100644 --- a/test/io/console/test_io_console.rb +++ b/test/io/console/test_io_console.rb @@ -26,14 +26,14 @@ def test_version end begin - PATHS = $LOADED_FEATURES.grep(%r"/io/console(?:\.#{RbConfig::CONFIG['DLEXT']}|\.rb|/\w+\.rb)\z") {$`} + paths = $LOADED_FEATURES.grep(%r"/io/console(?:\.#{RbConfig::CONFIG['DLEXT']}|\.rb|/\w+\.rb)\z") {$`} rescue Encoding::CompatibilityError $stderr.puts "test_io_console.rb debug" $LOADED_FEATURES.each{|path| $stderr.puts [path, path.encoding].inspect} raise end - PATHS.uniq! - INCLUDE_OPTS = "-I#{PATHS.join(File::PATH_SEPARATOR)}" + paths.uniq! + INCLUDE_OPTS = "-I#{paths.join(File::PATH_SEPARATOR)}".freeze # FreeBSD seems to hang on TTOU when running parallel tests # tested on FreeBSD 11.x. @@ -75,6 +75,7 @@ def test_bad_keyword end TTY_ENHANCED = IO.instance_method(:tty?).arity != 0 + TTY_MODE_STTY = IO.private_method_defined?(:_io_console_stty) def test_tty? pend "not supported" unless TTY_ENHANCED @@ -348,7 +349,9 @@ def test_getpass assert_equal("\r\n", r.gets) assert_equal("\"asdf\"", r.gets.chomp) end + end + def test_getpass_eof run_pty("p IO.console.getpass('> ')") do |r, w| assert_equal("> ", r.readpartial(10)) sleep 0.1 @@ -357,7 +360,9 @@ def test_getpass assert_equal("\r\n", r.gets) assert_equal("\"asdf\"", r.gets.chomp) end + end + def test_getpass_rs run_pty("$VERBOSE, $/ = nil, '.'; p IO.console.getpass('> ')") do |r, w| assert_equal("> ", r.readpartial(10)) sleep 0.1 @@ -380,7 +385,7 @@ def test_getpass_empty end def test_iflush - pend "stty cannot flush terminal queues" if IO.private_method_defined?(:_io_console_stty) + pend "stty cannot flush terminal queues" if TTY_MODE_STTY helper {|m, s| m.print "a" @@ -403,7 +408,7 @@ def test_oflush end def test_ioflush - pend "stty cannot flush terminal queues" if IO.private_method_defined?(:_io_console_stty) + pend "stty cannot flush terminal queues" if TTY_MODE_STTY helper {|m, s| m.print "a" diff --git a/test/io/console/test_ractor.rb b/test/io/console/test_ractor.rb index dff0c67eab0df0..374db1f25a4641 100644 --- a/test/io/console/test_ractor.rb +++ b/test/io/console/test_ractor.rb @@ -5,7 +5,7 @@ class TestIOConsoleInRactor < Test::Unit::TestCase def test_ractor ext = "/io/console.#{RbConfig::CONFIG['DLEXT']}" - path = $".find {|path| path.end_with?(ext)} + path = $".find {|path| path.end_with?(ext)} || "io/console" assert_in_out_err(%W[-r#{path}], "#{<<~"begin;"}\n#{<<~'end;'}", ["true"], []) begin; class Ractor From c907f6af4911b6ce6fbf5643d733e6718528cf00 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 2 Sep 2026 19:13:00 +0900 Subject: [PATCH 14/14] [ruby/io-console] Add some attributes to `Console::Mode` https://github.com/ruby/io-console/commit/adfe5c9f37 --- ext/io/console/console.c | 87 ++++++++++++++++++++++++++++-- test/io/console/test_io_console.rb | 23 ++++++++ 2 files changed, 106 insertions(+), 4 deletions(-) diff --git a/ext/io/console/console.c b/ext/io/console/console.c index eb1c876e9b91fe..d3f0a7d86041d0 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -178,6 +178,22 @@ typedef struct { # define NIL_OR_UNDEF_P(obj) (NIL_P(obj) || UNDEF_P(obj)) #endif +static int +to_vtime(VALUE vtime) +{ + VALUE v10 = INT2FIX(10); + vtime = rb_funcall3(vtime, '*', 1, &v10); + return NUM2INT(vtime); +} + +static unsigned char +clamp_uchar(int n) +{ + if ((unsigned int)n >= UCHAR_MAX) n = UCHAR_MAX; + else if (n < 0) n = 0; + return (unsigned char)n; +} + static rawmode_arg_t * rawmode_opt(int *argcp, VALUE *argv, int min_argc, int max_argc, rawmode_arg_t *opts) { @@ -209,13 +225,11 @@ rawmode_opt(int *argcp, VALUE *argv, int min_argc, int max_argc, rawmode_arg_t * opts->vtime = 0; opts->intr = 0; if (!NIL_OR_UNDEF_P(vmin)) { - opts->vmin = NUM2INT(vmin); + opts->vmin = clamp_uchar(NUM2INT(vmin)); optp = opts; } if (!NIL_OR_UNDEF_P(vtime)) { - VALUE v10 = INT2FIX(10); - vtime = rb_funcall3(vtime, '*', 1, &v10); - opts->vtime = NUM2INT(vtime); + opts->vtime = clamp_uchar(to_vtime(vtime)); optp = opts; } switch (intr) { @@ -762,6 +776,13 @@ conmode_init_copy(VALUE obj, VALUE obj2) return obj; } +static VALUE +conmode_get_echo(VALUE obj) +{ + conmode *t = rb_check_typeddata(obj, &conmode_type); + return echo_p(t) ? Qtrue : Qfalse; +} + static VALUE conmode_set_echo(VALUE obj, VALUE f) { @@ -794,6 +815,58 @@ conmode_raw_new(int argc, VALUE *argv, VALUE obj) return conmode_new(rb_obj_class(obj), &t); } +static VALUE +conmode_get_min(VALUE obj) +{ + conmode *t = rb_check_typeddata(obj, &conmode_type); +#ifdef VMIN + return INT2FIX(t->c_cc[VMIN]); +#else + (void)t; + return Qnil; +#endif +} + +static VALUE +conmode_set_min(VALUE obj, VALUE min) +{ + conmode *t = rb_check_typeddata(obj, &conmode_type); + int vmin = NIL_P(min) ? 1 : clamp_uchar(NUM2INT(min)); +#ifdef VMIN + t->c_cc[VMIN] = vmin; +#else + (void)t; + (void)vmin; +#endif + return min; +} + +static VALUE +conmode_get_time(VALUE obj) +{ + conmode *t = rb_check_typeddata(obj, &conmode_type); +#ifdef VTIME + return rb_rational_new(INT2FIX(t->c_cc[VTIME]), INT2FIX(10)); +#else + (void)t; + return Qnil; +#endif +} + +static VALUE +conmode_set_time(VALUE obj, VALUE time) +{ + conmode *t = rb_check_typeddata(obj, &conmode_type); + int vtime = NIL_P(time) ? 0 : clamp_uchar(to_vtime(time)); +#ifdef VTIME + t->c_cc[VTIME] = vtime; +#else + (void)t; + (void)vtime; +#endif + return time; +} + #ifdef _WIN32 /* * call-seq: @@ -2508,9 +2581,15 @@ InitVM_console(void) rb_define_alloc_func(cConmode, conmode_alloc); rb_undef_method(cConmode, "initialize"); rb_define_method(cConmode, "initialize_copy", conmode_init_copy, 1); + rb_define_method(cConmode, "echo?", conmode_get_echo, 0); + rb_define_method(cConmode, "echo", conmode_get_echo, 0); rb_define_method(cConmode, "echo=", conmode_set_echo, 1); rb_define_method(cConmode, "raw!", conmode_set_raw, -1); rb_define_method(cConmode, "raw", conmode_raw_new, -1); + rb_define_method(cConmode, "min", conmode_get_min, 0); + rb_define_method(cConmode, "min=", conmode_set_min, 1); + rb_define_method(cConmode, "time", conmode_get_time, 0); + rb_define_method(cConmode, "time=", conmode_set_time, 1); #ifdef _WIN32 rb_define_method(cConmode, "virtual_terminal_processing?", conmode_virtual_terminal_processing_p, 0); rb_define_method(cConmode, "virtual_terminal_processing=", conmode_set_virtual_terminal_processing, 1); diff --git a/test/io/console/test_io_console.rb b/test/io/console/test_io_console.rb index af207c820a5a5e..18782641f4b309 100644 --- a/test/io/console/test_io_console.rb +++ b/test/io/console/test_io_console.rb @@ -77,6 +77,18 @@ def test_bad_keyword TTY_ENHANCED = IO.instance_method(:tty?).arity != 0 TTY_MODE_STTY = IO.private_method_defined?(:_io_console_stty) + def test_stty_mode_arguments + omit "stty backend only" unless TTY_MODE_STTY + + mode = IO::Console::Mode.new( + "saved\n", + "echo icanon isig opost; min = 1; time = 0;", + ) + mode.raw!(min: 2, time: 0.3) + + assert_equal(["saved", "raw", "min", "2", "time", "3"], mode.arguments) + end + def test_tty? pend "not supported" unless TTY_ENHANCED @@ -294,8 +306,10 @@ def test_console_mode assert_same(IO::Console::Mode, IO.const_get(:ConsoleMode)) end + assert_predicate(original, :echo?) noecho = original.dup noecho.echo = false + assert_not_predicate(noecho, :echo?) assert_same(noecho, s.send(:console_mode=, noecho)) assert_not_predicate(s, :echo?) @@ -305,6 +319,15 @@ def test_console_mode assert_same(raw, s.send(:console_mode=, raw)) s.print "raw\n" assert_equal("raw\n", m.gets) + + if min = s.console_mode.min + assert_equal(1, min) + assert_equal(2, s.raw(min: 2) {s.console_mode.min}) + end + if time = s.console_mode.time + assert_equal(0, time) + assert_equal(3.1, s.raw(time: 3.14r) {s.console_mode.time}) + end ensure s.console_mode = original if original end