require "../spec_helper" require "../../support/finalize" class IO::FileDescriptor include FinalizeCounter end private CLOSE_ON_EXEC_AVAILABLE = {{ !flag?(:win32) }} private def shell_command(command) {% if flag?(:win32) %} "cmd.exe /c #{Process.quote(command)}" {% else %} "/bin/sh -c #{Process.quote(command)}" {% end %} end describe IO::FileDescriptor do describe "#initialize" do it "handles closed file descriptor gracefully" do a, b = IO.pipe a.close b.close fd = IO::FileDescriptor.new(a.fd) fd.closed?.should be_true end end it "reopen STDIN with the right mode", tags: %w[slow] do code = %q(puts "#{STDIN.blocking} #{STDIN.info.type}") compile_source(code) do |binpath| `#{shell_command %(#{Process.quote(binpath)} < #{Process.quote(binpath)})}`.chomp.should eq("true File") `#{shell_command %(echo "" | #{Process.quote(binpath)})}`.chomp.should eq("#{{{ flag?(:win32) }}} Pipe") end end describe "#tty?" do it "returns false for null device" do File.open(File::NULL) do |f| f.tty?.should be_false end end it "returns false for standard streams redirected to null device", tags: %w[slow] do code = %q(print STDIN.tty?, ' ', STDERR.tty?) compile_source(code) do |binpath| `#{shell_command %(#{Process.quote(binpath)} < #{File::NULL} 2> #{File::NULL})}`.should eq("false false") end end end describe "#finalize" do it "closes" do pipes = [] of IO::FileDescriptor assert_finalizes("fd") do a, b = IO.pipe pipes << b a end expect_raises(IO::Error) do pipes.each do |p| p.puts "123" end end end it "does not flush" do with_tempfile "fd-finalize-flush" do |path| file = File.new(path, "w") file << "foo" file.flush file << "bar" file.finalize File.read(path).should eq "foo" ensure file.try(&.close) rescue nil end end end it "opens STDIN in binary mode", tags: %w[slow] do code = %q(print STDIN.gets_to_end.includes?('\r')) compile_source(code) do |binpath| io_in = IO::Memory.new("foo\r\n") io_out = IO::Memory.new Process.run(binpath, input: io_in, output: io_out) io_out.to_s.should eq("true") end end it "opens STDOUT in binary mode", tags: %w[slow] do code = %q(puts "foo") compile_source(code) do |binpath| io = IO::Memory.new Process.run(binpath, output: io) io.to_s.should eq("foo\n") end end it "opens STDERR in binary mode", tags: %w[slow] do code = %q(STDERR.puts "foo") compile_source(code) do |binpath| io = IO::Memory.new Process.run(binpath, error: io) io.to_s.should eq("foo\n") end end it "does not close if close_on_finalize is false" do pipes = [] of IO::FileDescriptor assert_finalizes("fd") do a, b = IO.pipe a.close_on_finalize = false pipes << b a end pipes.each do |p| p.puts "123" end end it "reopens" do File.open(datapath("test_file.txt")) do |file1| File.open(datapath("test_file.ini")) do |file2| file2.reopen(file1) file2.gets.should eq("Hello World") end end end describe "close_on_exec" do it "sets close on exec on the reopened standard descriptors" do unless STDIN.fd == Crystal::System::FileDescriptor::STDIN_HANDLE STDIN.close_on_exec?.should be_true end unless STDOUT.fd == Crystal::System::FileDescriptor::STDOUT_HANDLE STDOUT.close_on_exec?.should be_true end unless STDERR.fd == Crystal::System::FileDescriptor::STDERR_HANDLE STDERR.close_on_exec?.should be_true end end it "is enabled by default (open)" do File.open(datapath("test_file.txt")) do |file| file.close_on_exec?.should eq CLOSE_ON_EXEC_AVAILABLE end end it "is enabled by default (pipe)" do IO::FileDescriptor.pipe.each do |fd| fd.close_on_exec?.should eq CLOSE_ON_EXEC_AVAILABLE fd.close_on_exec?.should eq CLOSE_ON_EXEC_AVAILABLE end end it "can be disabled and reenabled" do File.open(datapath("test_file.txt")) do |file| file.close_on_exec = false file.close_on_exec?.should be_false if CLOSE_ON_EXEC_AVAILABLE file.close_on_exec = true file.close_on_exec?.should be_true else expect_raises(NotImplementedError) do file.close_on_exec = true end end end end if CLOSE_ON_EXEC_AVAILABLE it "is copied on reopen" do File.open(datapath("test_file.txt")) do |file1| file1.close_on_exec = true File.open(datapath("test_file.ini")) do |file2| file2.reopen(file1) file2.close_on_exec?.should be_true end file1.close_on_exec = false File.open(datapath("test_file.ini")) do |file3| file3.reopen(file1) file3.close_on_exec?.should be_false end end end end end it ".set_blocking and .get_blocking" do File.open(datapath("test_file.txt"), "r") do |file| fd = file.fd {% if flag?(:win32) %} expect_raises(NotImplementedError) { IO::FileDescriptor.set_blocking(fd, false) } expect_raises(NotImplementedError) { IO::FileDescriptor.set_blocking(fd, true) } expect_raises(NotImplementedError) { IO::FileDescriptor.get_blocking(fd) } {% else %} IO::FileDescriptor.set_blocking(fd, false) IO::FileDescriptor.get_blocking(fd).should be_false IO::FileDescriptor.set_blocking(fd, true) IO::FileDescriptor.get_blocking(fd).should be_true {% end %} end end typeof(STDIN.noecho { }) typeof(STDIN.noecho!) typeof(STDIN.echo { }) typeof(STDIN.echo!) typeof(STDIN.cooked { }) typeof(STDIN.cooked!) typeof(STDIN.raw { }) typeof(STDIN.raw!) end