require_relative '../../spec_helper'
require_relative 'shared/open'

describe "File.new" do
  before :each do
    @file = tmp('test.txt')
    @fh = nil
    @flags = File::CREAT | File::TRUNC | File::WRONLY
    touch @file
  end

  after :each do
    @fh.close if @fh
    rm_r @file
  end

  it "returns a new File with mode string" do
    @fh = File.new(@file, 'w')
    @fh.should.is_a?(File)
    File.should.exist?(@file)
  end

  it "returns a new File with mode num" do
    @fh = File.new(@file, @flags)
    @fh.should.is_a?(File)
    File.should.exist?(@file)
  end

  it "returns a new File with modus num and permissions" do
    rm_r @file
    File.umask(0011)
    @fh = File.new(@file, @flags, 0755)
    @fh.should.is_a?(File)
    platform_is_not :windows do
      File.stat(@file).mode.to_s(8).should == "100744"
    end
    File.should.exist?(@file)
  end

  platform_is :darwin do
    it "returns a new File when given a path in a non-UTF-8, ASCII-compatible encoding containing non-ASCII characters" do
      utf8_path = tmp("file_new_utf8_path_\u{3042}.txt")
      # Can fail with UndefinedConversionError if tmp path has non-Shift_JIS chars (e.g. Emojis, Hangul, Cyrillic, accented letters)
      non_utf8_path = utf8_path.encode(Encoding::Windows_31J)

      begin
        @fh = File.new(non_utf8_path, "w")
        @fh.should.is_a?(File)
        File.should.exist?(utf8_path)
      ensure
        @fh.close if @fh and not @fh.closed?
        rm_r utf8_path
        rm_r non_utf8_path
      end
    end
  end

  it "creates the file and returns writable descriptor when called with 'w' mode and r-o permissions" do
    # it should be possible to write to such a file via returned descriptor,
    # even though the file permissions are r-r-r.

    rm_r @file
    begin
      f = File.new(@file, "w", 0444)
      -> { f.puts("test") }.should_not.raise(IOError)
    ensure
      f.close
    end
    File.should.exist?(@file)
    File.read(@file).should == "test\n"
  end

  platform_is_not :windows do
    it "opens the existing file, does not change permissions even when they are specified" do
      File.chmod(0644, @file)           # r-w perms
      orig_perms = File.stat(@file).mode & 0777
      begin
        f = File.new(@file, "w", 0444)    # r-o perms, but they should be ignored
        f.puts("test")
      ensure
        f.close
      end
      perms = File.stat(@file).mode & 0777
      perms.should == orig_perms

      # it should be still possible to read from the file
      File.read(@file).should == "test\n"
    end
  end

  it "returns a new File with modus fd" do
    @fh = File.new(@file)
    fh_copy = File.new(@fh.fileno)
    fh_copy.autoclose = false
    fh_copy.should.is_a?(File)
    File.should.exist?(@file)
  end

  it "returns a new read-only File when mode is not specified" do
    @fh = File.new(@file)

    -> { @fh.puts("test") }.should.raise(IOError)
    @fh.read.should == ""
    File.should.exist?(@file)
  end

  it "returns a new read-only File when mode is not specified but flags option is present" do
    @fh = File.new(@file, flags: File::CREAT)

    -> { @fh.puts("test") }.should.raise(IOError)
    @fh.read.should == ""
    File.should.exist?(@file)
  end

  it "creates a new file when use File::EXCL mode" do
    @fh = File.new(@file, File::EXCL)
    @fh.should.is_a?(File)
    File.should.exist?(@file)
  end

  it "raises an Errno::EEXIST if the file exists when create a new file with File::CREAT|File::EXCL" do
    -> { @fh = File.new(@file, File::CREAT|File::EXCL) }.should.raise(Errno::EEXIST)
  end

  it "creates a new file when use File::WRONLY|File::APPEND mode" do
    @fh = File.new(@file, File::WRONLY|File::APPEND)
    @fh.should.is_a?(File)
    File.should.exist?(@file)
  end

  it "returns a new File when use File::APPEND mode" do
    @fh = File.new(@file, File::APPEND)
    @fh.should.is_a?(File)
    File.should.exist?(@file)
  end

  it "returns a new File when use File::RDONLY|File::APPEND mode" do
    @fh = File.new(@file, File::RDONLY|File::APPEND)
    @fh.should.is_a?(File)
    File.should.exist?(@file)
  end

  it "returns a new File when use File::RDONLY|File::WRONLY mode" do
    @fh = File.new(@file, File::RDONLY|File::WRONLY)
    @fh.should.is_a?(File)
    File.should.exist?(@file)
  end

  it "creates a new file when use File::WRONLY|File::TRUNC mode" do
    @fh = File.new(@file, File::WRONLY|File::TRUNC)
    @fh.should.is_a?(File)
    File.should.exist?(@file)
  end

  it "returns a new read-only File when use File::RDONLY|File::CREAT mode" do
    @fh = File.new(@file, File::RDONLY|File::CREAT)
    @fh.should.is_a?(File)
    File.should.exist?(@file)

    # it's read-only
    -> { @fh.puts("test") }.should.raise(IOError)
    @fh.read.should == ""
  end

  it "returns a new read-only File when use File::CREAT mode" do
    @fh = File.new(@file, File::CREAT)
    @fh.should.is_a?(File)
    File.should.exist?(@file)

    # it's read-only
    -> { @fh.puts("test") }.should.raise(IOError)
    @fh.read.should == ""
  end

  it "coerces filename using to_str" do
    name = mock("file")
    name.should_receive(:to_str).and_return(@file)
    @fh = File.new(name, "w")
    File.should.exist?(@file)
  end

  it "coerces filename using #to_path" do
    name = mock("file")
    name.should_receive(:to_path).and_return(@file)
    @fh = File.new(name, "w")
    File.should.exist?(@file)
  end

  it "accepts options as a keyword argument" do
    @fh = File.new(@file, 'w', 0755, flags: @flags)
    @fh.should.is_a?(File)
    @fh.close

    -> {
      @fh = File.new(@file, 'w', 0755, {flags: @flags})
    }.should.raise(ArgumentError, "wrong number of arguments (given 4, expected 1..3)")
  end

  it "bitwise-ORs mode and flags option" do
    -> {
      @fh = File.new(@file, 'w', flags: File::EXCL)
    }.should.raise(Errno::EEXIST, /File exists/)

    -> {
      @fh = File.new(@file, mode: 'w', flags: File::EXCL)
    }.should.raise(Errno::EEXIST, /File exists/)
  end

  it "does not use the given block and warns to use File::open" do
    -> {
      @fh = File.new(@file) { raise }
    }.should complain(/warning: File::new\(\) does not take block; use File::open\(\) instead/)
  end

  it "raises a TypeError if the first parameter can't be coerced to a string" do
    -> { File.new(true) }.should.raise(TypeError)
    -> { File.new(false) }.should.raise(TypeError)
  end

  it "raises a TypeError if the first parameter is nil" do
    -> { File.new(nil) }.should.raise(TypeError)
  end

  it "raises an Errno::EBADF if the first parameter is an invalid file descriptor" do
    -> { File.new(-1) }.should.raise(Errno::EBADF)
  end

  platform_is_not :windows do
    it "can't alter mode or permissions when opening a file" do
      @fh = File.new(@file)
      -> {
        f = File.new(@fh.fileno, @flags)
        f.autoclose = false
      }.should.raise(Errno::EINVAL)
    end
  end

  platform_is_not :windows do
    it_behaves_like :open_directory, :new
  end
end
