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/ext/io/console/console.c b/ext/io/console/console.c index ac97dc32feb715..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: @@ -2135,10 +2208,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 +2241,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; } @@ -2223,7 +2302,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 @@ -2238,17 +2317,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); } @@ -2502,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/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 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/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 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 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 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 diff --git a/test/io/console/test_io_console.rb b/test/io/console/test_io_console.rb index 48743fc296600c..18782641f4b309 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 @@ -25,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. @@ -74,6 +75,19 @@ 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_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 @@ -292,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?) @@ -303,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 @@ -347,7 +372,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 @@ -356,7 +383,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 @@ -367,8 +396,19 @@ 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) + pend "stty cannot flush terminal queues" if TTY_MODE_STTY helper {|m, s| m.print "a" @@ -391,7 +431,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" @@ -456,6 +496,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 @@ -464,6 +506,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 @@ -691,7 +741,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") @@ -701,12 +751,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) @@ -720,11 +771,30 @@ 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 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 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