summaryrefslogtreecommitdiff
path: root/spec/ruby/core/io/shared/write.rb
blob: 964064746a1bb33fdf4ede30f32f69e45767fe44 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# encoding: utf-8
require_relative '../fixtures/classes'

describe :io_write, shared: true do
  before :each do
    @filename = tmp("IO_syswrite_file") + $$.to_s
    File.open(@filename, "w") do |file|
      file.send(@method, "012345678901234567890123456789")
    end
    @file = File.open(@filename, "r+")
    @readonly_file = File.open(@filename)
  end

  after :each do
    @readonly_file.close if @readonly_file
    @file.close if @file
    rm_r @filename
  end

  it "coerces the argument to a string using to_s" do
    (obj = mock('test')).should_receive(:to_s).and_return('a string')
    @file.send(@method, obj)
  end

  it "checks if the file is writable if writing more than zero bytes" do
    -> { @readonly_file.send(@method, "abcde") }.should raise_error(IOError)
  end

  it "returns the number of bytes written" do
    written = @file.send(@method, "abcde")
    written.should == 5
  end

  it "invokes to_s on non-String argument" do
    data = "abcdefgh9876"
    (obj = mock(data)).should_receive(:to_s).and_return(data)
    @file.send(@method, obj)
    @file.seek(0)
    @file.read(data.size).should == data
  end

  it "writes all of the string's bytes without buffering if mode is sync" do
    @file.sync = true
    written = @file.send(@method, "abcde")
    written.should == 5
    File.open(@filename) do |file|
      file.read(10).should == "abcde56789"
    end
  end

  it "does not warn if called after IO#read" do
    @file.read(5)
    -> { @file.send(@method, "fghij") }.should_not complain
  end

  it "writes to the current position after IO#read" do
    @file.read(5)
    @file.send(@method, "abcd")
    @file.rewind
    @file.read.should == "01234abcd901234567890123456789"
  end

  it "advances the file position by the count of given bytes" do
    @file.send(@method, "abcde")
    @file.read(10).should == "5678901234"
  end

  it "raises IOError on closed stream" do
    -> { IOSpecs.closed_io.send(@method, "hello") }.should raise_error(IOError)
  end

  describe "on a pipe" do
    before :each do
      @r, @w = IO.pipe
    end

    after :each do
      @r.close
      @w.close
    end

    it "writes the given String to the pipe" do
      @w.send(@method, "foo")
      @w.close
      @r.read.should == "foo"
    end

    # [ruby-core:90895] RJIT worker may leave fd open in a forked child.
    # For instance, RJIT creates a worker before @r.close with fork(), @r.close happens,
    # and the RJIT worker keeps the pipe open until the worker execve().
    # TODO: consider acquiring GVL from RJIT worker.
    guard_not -> { defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? } do
      it "raises Errno::EPIPE if the read end is closed and does not die from SIGPIPE" do
        @r.close
        -> { @w.send(@method, "foo") }.should raise_error(Errno::EPIPE, /Broken pipe/)
      end
    end
  end
end

describe :io_write_transcode, shared: true do
  before :each do
    @transcode_filename = tmp("io_write_transcode")
  end

  after :each do
    rm_r @transcode_filename
  end

  it "transcodes the given string when the external encoding is set and neither is BINARY" do
    utf8_str = "hello"

    File.open(@transcode_filename, "w", external_encoding: Encoding::UTF_16BE) do |file|
      file.external_encoding.should == Encoding::UTF_16BE
      file.send(@method, utf8_str)
    end

    result = File.binread(@transcode_filename)
    expected = [0, 104, 0, 101, 0, 108, 0, 108, 0, 111] # UTF-16BE bytes for "hello"

    result.bytes.should == expected
  end

  it "transcodes the given string when the external encoding is set and the string encoding is BINARY" do
    str = "été".b

    File.open(@transcode_filename, "w", external_encoding: Encoding::UTF_16BE) do |file|
      file.external_encoding.should == Encoding::UTF_16BE
      -> { file.send(@method, str) }.should raise_error(Encoding::UndefinedConversionError)
    end
  end
end

describe :io_write_no_transcode, shared: true do
  before :each do
    @transcode_filename = tmp("io_write_no_transcode")
  end

  after :each do
    rm_r @transcode_filename
  end

  it "does not transcode the given string even when the external encoding is set" do
    utf8_str = "hello"

    File.open(@transcode_filename, "w", external_encoding: Encoding::UTF_16BE) do |file|
      file.external_encoding.should == Encoding::UTF_16BE
      file.send(@method, utf8_str)
    end

    result = File.binread(@transcode_filename)
    result.bytes.should == utf8_str.bytes
  end
end