1Code.require_file("test_helper.exs", __DIR__)
2
3defmodule StringIOTest do
4  use ExUnit.Case, async: true
5
6  doctest StringIO
7
8  test "open and close" do
9    {:ok, pid} = StringIO.open("")
10    assert StringIO.close(pid) == {:ok, {"", ""}}
11  end
12
13  test "contents" do
14    {:ok, pid} = StringIO.open("abc")
15    IO.write(pid, "edf")
16    assert StringIO.contents(pid) == {"abc", "edf"}
17  end
18
19  test "flush" do
20    {:ok, pid} = StringIO.open("")
21    IO.write(pid, "edf")
22    assert StringIO.flush(pid) == "edf"
23    assert StringIO.contents(pid) == {"", ""}
24  end
25
26  ## IO module
27
28  test "IO.read :line with \\n" do
29    {:ok, pid} = StringIO.open("abc\n")
30    assert IO.read(pid, :line) == "abc\n"
31    assert IO.read(pid, :line) == :eof
32    assert StringIO.contents(pid) == {"", ""}
33  end
34
35  test "IO.read :line with \\rn" do
36    {:ok, pid} = StringIO.open("abc\r\n")
37    assert IO.read(pid, :line) == "abc\n"
38    assert IO.read(pid, :line) == :eof
39    assert StringIO.contents(pid) == {"", ""}
40  end
41
42  test "IO.read :line without line break" do
43    {:ok, pid} = StringIO.open("abc")
44    assert IO.read(pid, :line) == "abc"
45    assert IO.read(pid, :line) == :eof
46    assert StringIO.contents(pid) == {"", ""}
47  end
48
49  test "IO.read :line with UTF-8" do
50    {:ok, pid} = StringIO.open("⼊\n")
51    assert IO.read(pid, :line) == "⼊\n"
52    assert IO.read(pid, :line) == :eof
53    assert StringIO.contents(pid) == {"", ""}
54  end
55
56  test "IO.read :line with invalid UTF-8" do
57    {:ok, pid} = StringIO.open(<<130, 227, 129, 132, 227, 129, 134>>)
58    assert IO.read(pid, :line) == {:error, :collect_line}
59    assert StringIO.contents(pid) == {<<130, 227, 129, 132, 227, 129, 134>>, ""}
60  end
61
62  test "IO.read count" do
63    {:ok, pid} = StringIO.open("abc")
64    assert IO.read(pid, 2) == "ab"
65    assert IO.read(pid, 8) == "c"
66    assert IO.read(pid, 1) == :eof
67    assert StringIO.contents(pid) == {"", ""}
68  end
69
70  test "IO.read count with UTF-8" do
71    {:ok, pid} = StringIO.open("あいう")
72    assert IO.read(pid, 2) == "あい"
73    assert IO.read(pid, 8) == "う"
74    assert IO.read(pid, 1) == :eof
75    assert StringIO.contents(pid) == {"", ""}
76  end
77
78  test "IO.read count with invalid UTF-8" do
79    {:ok, pid} = StringIO.open(<<130, 227, 129, 132, 227, 129, 134>>)
80    assert IO.read(pid, 2) == {:error, :invalid_unicode}
81    assert StringIO.contents(pid) == {<<130, 227, 129, 132, 227, 129, 134>>, ""}
82  end
83
84  test "IO.binread :line with \\n" do
85    {:ok, pid} = StringIO.open("abc\n")
86    assert IO.binread(pid, :line) == "abc\n"
87    assert IO.binread(pid, :line) == :eof
88    assert StringIO.contents(pid) == {"", ""}
89  end
90
91  test "IO.binread :line with \\r\\n" do
92    {:ok, pid} = StringIO.open("abc\r\n")
93    assert IO.binread(pid, :line) == "abc\n"
94    assert IO.binread(pid, :line) == :eof
95    assert StringIO.contents(pid) == {"", ""}
96  end
97
98  test "IO.binread :line without line break" do
99    {:ok, pid} = StringIO.open("abc")
100    assert IO.binread(pid, :line) == "abc"
101    assert IO.binread(pid, :line) == :eof
102    assert StringIO.contents(pid) == {"", ""}
103  end
104
105  test "IO.binread :line with raw bytes" do
106    {:ok, pid} = StringIO.open(<<181, 255, 194, ?\n>>)
107    assert IO.binread(pid, :line) == <<181, 255, 194, ?\n>>
108    assert IO.binread(pid, :line) == :eof
109    assert StringIO.contents(pid) == {"", ""}
110  end
111
112  test "IO.binread count" do
113    {:ok, pid} = StringIO.open("abc")
114    assert IO.binread(pid, 2) == "ab"
115    assert IO.binread(pid, 8) == "c"
116    assert IO.binread(pid, 1) == :eof
117    assert StringIO.contents(pid) == {"", ""}
118  end
119
120  test "IO.binread count with UTF-8" do
121    {:ok, pid} = StringIO.open("あいう")
122    assert IO.binread(pid, 2) == <<227, 129>>
123    assert IO.binread(pid, 8) == <<130, 227, 129, 132, 227, 129, 134>>
124    assert IO.binread(pid, 1) == :eof
125    assert StringIO.contents(pid) == {"", ""}
126  end
127
128  test "IO.write" do
129    {:ok, pid} = StringIO.open("")
130    assert IO.write(pid, "foo") == :ok
131    assert StringIO.contents(pid) == {"", "foo"}
132  end
133
134  test "IO.write with UTF-8" do
135    {:ok, pid} = StringIO.open("")
136    assert IO.write(pid, "あいう") == :ok
137    assert StringIO.contents(pid) == {"", "あいう"}
138  end
139
140  test "IO.write with non-printable arguments" do
141    {:ok, pid} = StringIO.open("")
142
143    assert_raise ArgumentError, fn ->
144      IO.write(pid, [<<1::1>>])
145    end
146  end
147
148  test "IO.binwrite" do
149    {:ok, pid} = StringIO.open("")
150    assert IO.binwrite(pid, "foo") == :ok
151    assert StringIO.contents(pid) == {"", "foo"}
152  end
153
154  test "IO.binwrite with UTF-8" do
155    {:ok, pid} = StringIO.open("")
156    assert IO.binwrite(pid, "あいう") == :ok
157
158    binary =
159      <<195, 163, 194, 129, 194, 130, 195, 163>> <>
160        <<194, 129, 194, 132, 195, 163, 194, 129, 194, 134>>
161
162    assert StringIO.contents(pid) == {"", binary}
163  end
164
165  test "IO.binwrite with bytes" do
166    {:ok, pid} = StringIO.open("")
167    assert IO.binwrite(pid, <<127, 128>>) == :ok
168    assert StringIO.contents(pid) == {"", <<127, 194, 128>>}
169  end
170
171  test "IO.binwrite with bytes and latin1 encoding" do
172    {:ok, pid} = StringIO.open("", encoding: :latin1)
173    assert IO.binwrite(pid, <<127, 128>>) == :ok
174    assert StringIO.contents(pid) == {"", <<127, 128>>}
175  end
176
177  test "IO.puts" do
178    {:ok, pid} = StringIO.open("")
179    assert IO.puts(pid, "abc") == :ok
180    assert StringIO.contents(pid) == {"", "abc\n"}
181  end
182
183  test "IO.puts with non-printable arguments" do
184    {:ok, pid} = StringIO.open("")
185
186    assert_raise ArgumentError, fn ->
187      IO.puts(pid, [<<1::1>>])
188    end
189  end
190
191  test "IO.inspect" do
192    {:ok, pid} = StringIO.open("")
193    assert IO.inspect(pid, {}, []) == {}
194    assert StringIO.contents(pid) == {"", "{}\n"}
195  end
196
197  test "IO.getn" do
198    {:ok, pid} = StringIO.open("abc")
199    assert IO.getn(pid, ">", 2) == "ab"
200    assert StringIO.contents(pid) == {"c", ""}
201  end
202
203  test "IO.getn with UTF-8" do
204    {:ok, pid} = StringIO.open("あいう")
205    assert IO.getn(pid, ">", 2) == "あい"
206    assert StringIO.contents(pid) == {"う", ""}
207  end
208
209  test "IO.getn with invalid UTF-8" do
210    {:ok, pid} = StringIO.open(<<130, 227, 129, 132, 227, 129, 134>>)
211    assert IO.getn(pid, ">", 2) == {:error, :invalid_unicode}
212    assert StringIO.contents(pid) == {<<130, 227, 129, 132, 227, 129, 134>>, ""}
213  end
214
215  test "IO.getn with capture_prompt" do
216    {:ok, pid} = StringIO.open("abc", capture_prompt: true)
217    assert IO.getn(pid, ">", 2) == "ab"
218    assert StringIO.contents(pid) == {"c", ">"}
219  end
220
221  test "IO.gets with \\n" do
222    {:ok, pid} = StringIO.open("abc\nd")
223    assert IO.gets(pid, ">") == "abc\n"
224    assert StringIO.contents(pid) == {"d", ""}
225  end
226
227  test "IO.gets with \\r\\n" do
228    {:ok, pid} = StringIO.open("abc\r\nd")
229    assert IO.gets(pid, ">") == "abc\n"
230    assert StringIO.contents(pid) == {"d", ""}
231  end
232
233  test "IO.gets without line breaks" do
234    {:ok, pid} = StringIO.open("abc")
235    assert IO.gets(pid, ">") == "abc"
236    assert StringIO.contents(pid) == {"", ""}
237  end
238
239  test "IO.gets with invalid UTF-8" do
240    {:ok, pid} = StringIO.open(<<130, 227, 129, 132, 227, 129, 134>>)
241    assert IO.gets(pid, ">") == {:error, :collect_line}
242    assert StringIO.contents(pid) == {<<130, 227, 129, 132, 227, 129, 134>>, ""}
243  end
244
245  test "IO.gets with capture_prompt" do
246    {:ok, pid} = StringIO.open("abc\n", capture_prompt: true)
247    assert IO.gets(pid, ">") == "abc\n"
248    assert StringIO.contents(pid) == {"", ">"}
249  end
250
251  test ":io.get_password" do
252    {:ok, pid} = StringIO.open("abc\n")
253    assert :io.get_password(pid) == "abc\n"
254    assert StringIO.contents(pid) == {"", ""}
255  end
256
257  test "IO.stream" do
258    {:ok, pid} = StringIO.open("abc")
259    assert IO.stream(pid, 2) |> Enum.to_list() == ["ab", "c"]
260    assert StringIO.contents(pid) == {"", ""}
261  end
262
263  test "IO.stream with invalid UTF-8" do
264    {:ok, pid} = StringIO.open(<<130, 227, 129, 132, 227, 129, 134>>)
265
266    assert_raise IO.StreamError, fn ->
267      IO.stream(pid, 2) |> Enum.to_list()
268    end
269
270    assert StringIO.contents(pid) == {<<130, 227, 129, 132, 227, 129, 134>>, ""}
271  end
272
273  test "IO.binstream" do
274    {:ok, pid} = StringIO.open("abc")
275    assert IO.stream(pid, 2) |> Enum.to_list() == ["ab", "c"]
276    assert StringIO.contents(pid) == {"", ""}
277  end
278
279  defp get_until(pid, encoding, prompt, module, function) do
280    :io.request(pid, {:get_until, encoding, prompt, module, function, []})
281  end
282
283  defmodule GetUntilCallbacks do
284    def until_eof(continuation, :eof) do
285      {:done, continuation, :eof}
286    end
287
288    def until_eof(continuation, content) do
289      {:more, continuation ++ content}
290    end
291
292    def until_eof_then_try_more('magic-stop-prefix' ++ continuation, :eof) do
293      {:done, continuation, :eof}
294    end
295
296    def until_eof_then_try_more(continuation, :eof) do
297      {:more, 'magic-stop-prefix' ++ continuation}
298    end
299
300    def until_eof_then_try_more(continuation, content) do
301      {:more, continuation ++ content}
302    end
303
304    def up_to_3_bytes(continuation, :eof) do
305      {:done, continuation, :eof}
306    end
307
308    def up_to_3_bytes(continuation, content) do
309      case continuation ++ content do
310        [a, b, c | tail] -> {:done, [a, b, c], tail}
311        str -> {:more, str}
312      end
313    end
314
315    def up_to_3_bytes_discard_rest(continuation, :eof) do
316      {:done, continuation, :eof}
317    end
318
319    def up_to_3_bytes_discard_rest(continuation, content) do
320      case continuation ++ content do
321        [a, b, c | _tail] -> {:done, [a, b, c], :eof}
322        str -> {:more, str}
323      end
324    end
325  end
326
327  test "get_until with up_to_3_bytes" do
328    {:ok, pid} = StringIO.open("abcdefg")
329    result = get_until(pid, :unicode, "", GetUntilCallbacks, :up_to_3_bytes)
330    assert result == "abc"
331    assert IO.read(pid, :all) == "defg"
332  end
333
334  test "get_until with up_to_3_bytes_discard_rest" do
335    {:ok, pid} = StringIO.open("abcdefg")
336    result = get_until(pid, :unicode, "", GetUntilCallbacks, :up_to_3_bytes_discard_rest)
337    assert result == "abc"
338    assert IO.read(pid, :all) == ""
339  end
340
341  test "get_until with until_eof" do
342    {:ok, pid} = StringIO.open("abc\nd")
343    result = get_until(pid, :unicode, "", GetUntilCallbacks, :until_eof)
344    assert result == "abc\nd"
345  end
346
347  test "get_until with until_eof and \\r\\n" do
348    {:ok, pid} = StringIO.open("abc\r\nd")
349    result = get_until(pid, :unicode, "", GetUntilCallbacks, :until_eof)
350    assert result == "abc\r\nd"
351  end
352
353  test "get_until with until_eof capturing prompt" do
354    {:ok, pid} = StringIO.open("abc\nd", capture_prompt: true)
355    result = get_until(pid, :unicode, ">", GetUntilCallbacks, :until_eof)
356    assert result == "abc\nd"
357    assert StringIO.contents(pid) == {"", ">>>"}
358  end
359
360  test "get_until with until_eof_then_try_more" do
361    {:ok, pid} = StringIO.open("abc\nd")
362    result = get_until(pid, :unicode, "", GetUntilCallbacks, :until_eof_then_try_more)
363    assert result == "abc\nd"
364  end
365
366  test "get_until with invalid UTF-8" do
367    {:ok, pid} = StringIO.open(<<130, 227, 129, 132, 227, 129, 134>>)
368    result = get_until(pid, :unicode, "", GetUntilCallbacks, :until_eof)
369    assert result == :error
370  end
371
372  test "get_until with raw bytes (latin1)" do
373    {:ok, pid} = StringIO.open(<<181, 255, 194, ?\n>>)
374    result = get_until(pid, :latin1, "", GetUntilCallbacks, :until_eof)
375    assert result == <<181, 255, 194, ?\n>>
376  end
377
378  test ":io.erl_scan_form/2" do
379    {:ok, pid} = StringIO.open("1.")
380    result = :io.scan_erl_form(pid, 'p>')
381    assert result == {:ok, [{:integer, 1, 1}, {:dot, 1}], 1}
382    assert StringIO.contents(pid) == {"", ""}
383  end
384
385  test ":io.erl_scan_form/2 with capture_prompt" do
386    {:ok, pid} = StringIO.open("1.", capture_prompt: true)
387    result = :io.scan_erl_form(pid, 'p>')
388    assert result == {:ok, [{:integer, 1, 1}, {:dot, 1}], 1}
389    assert StringIO.contents(pid) == {"", "p>p>"}
390  end
391end
392