|
1 | 1 | :- module(get_n_chars_tests, []). |
2 | 2 | :- use_module(test_framework). |
3 | 3 | :- use_module(library(charsio)). |
| 4 | +:- use_module(library(iso_ext)). |
4 | 5 | :- use_module(library(process)). |
5 | 6 | :- use_module(library(format)). |
6 | 7 | :- use_module(library(lists)). |
|
9 | 10 | test("timeout=0 equals get_n_chars/3", ( |
10 | 11 | atom_chars('/bin/echo', Echo), |
11 | 12 | atom_chars('ABCDEFGHIJ', Content), |
12 | | - process_create(Echo, [Content], [stdout(pipe(Out1))]), |
13 | | - process_create(Echo, [Content], [stdout(pipe(Out2))]), |
14 | | - |
15 | | - get_n_chars(Out1, 5, Chars1), |
16 | | - get_n_chars(Out2, 5, Chars2, 0), |
17 | | - |
18 | | - Chars1 = Chars2, |
19 | | - close(Out1), |
20 | | - close(Out2) |
| 13 | + iso_ext:setup_call_cleanup( |
| 14 | + process:process_create(Echo, [Content], [stdout(pipe(Out1))]), |
| 15 | + iso_ext:setup_call_cleanup( |
| 16 | + process:process_create(Echo, [Content], [stdout(pipe(Out2))]), |
| 17 | + ( |
| 18 | + charsio:get_n_chars(Out1, 5, Chars1), |
| 19 | + charsio:get_n_chars(Out2, 5, Chars2, 0), |
| 20 | + Chars1 = Chars2 |
| 21 | + ), |
| 22 | + close(Out2) |
| 23 | + ), |
| 24 | + close(Out1) |
| 25 | + ) |
21 | 26 | )). |
22 | 27 |
|
23 | 28 | % Test 2: Variable N with timeout=0 |
24 | 29 | test("variable N with timeout=0", ( |
25 | 30 | atom_chars('/bin/echo', Echo), |
26 | 31 | atom_chars('Testing', Content), |
27 | | - process_create(Echo, [Content], [stdout(pipe(Out1))]), |
28 | | - process_create(Echo, [Content], [stdout(pipe(Out2))]), |
29 | | - |
30 | | - get_n_chars(Out1, N1, Chars1), |
31 | | - get_n_chars(Out2, N2, Chars2, 0), |
32 | | - |
33 | | - N1 = N2, |
34 | | - Chars1 = Chars2, |
35 | | - N1 = 8, |
36 | | - Chars1 = "Testing\n", |
37 | | - close(Out1), |
38 | | - close(Out2) |
| 32 | + iso_ext:setup_call_cleanup( |
| 33 | + process:process_create(Echo, [Content], [stdout(pipe(Out1))]), |
| 34 | + iso_ext:setup_call_cleanup( |
| 35 | + process:process_create(Echo, [Content], [stdout(pipe(Out2))]), |
| 36 | + ( |
| 37 | + charsio:get_n_chars(Out1, N1, Chars1), |
| 38 | + charsio:get_n_chars(Out2, N2, Chars2, 0), |
| 39 | + N1 = N2, |
| 40 | + Chars1 = Chars2, |
| 41 | + N1 = 8, |
| 42 | + Chars1 = "Testing\n" |
| 43 | + ), |
| 44 | + close(Out2) |
| 45 | + ), |
| 46 | + close(Out1) |
| 47 | + ) |
39 | 48 | )). |
40 | 49 |
|
41 | 50 | % Test 3: Negative timeout should also mean no timeout |
42 | 51 | test("negative timeout equals no timeout", ( |
43 | 52 | atom_chars('/bin/echo', Echo), |
44 | 53 | atom_chars('NegativeTest', Content), |
45 | | - process_create(Echo, [Content], [stdout(pipe(Out1))]), |
46 | | - process_create(Echo, [Content], [stdout(pipe(Out2))]), |
47 | | - |
48 | | - get_n_chars(Out1, N1, Chars1), |
49 | | - get_n_chars(Out2, N2, Chars2, -100), |
50 | | - |
51 | | - N1 = N2, |
52 | | - Chars1 = Chars2, |
53 | | - close(Out1), |
54 | | - close(Out2) |
| 54 | + iso_ext:setup_call_cleanup( |
| 55 | + process:process_create(Echo, [Content], [stdout(pipe(Out1))]), |
| 56 | + iso_ext:setup_call_cleanup( |
| 57 | + process:process_create(Echo, [Content], [stdout(pipe(Out2))]), |
| 58 | + ( |
| 59 | + charsio:get_n_chars(Out1, N1, Chars1), |
| 60 | + charsio:get_n_chars(Out2, N2, Chars2, -100), |
| 61 | + N1 = N2, |
| 62 | + Chars1 = Chars2 |
| 63 | + ), |
| 64 | + close(Out2) |
| 65 | + ), |
| 66 | + close(Out1) |
| 67 | + ) |
55 | 68 | )). |
56 | 69 |
|
57 | 70 | % Test 4: Positive timeout should timeout on slow output |
58 | 71 | test("positive timeout stops reading", ( |
59 | 72 | atom_chars('/usr/bin/python3', Py), |
60 | 73 | atom_chars('-c', C), |
61 | 74 | atom_chars('import sys,time; [print(c,end="",flush=True) or time.sleep(1) for c in "ABCDEFGH"]', Cmd), |
62 | | - process_create(Py, [C, Cmd], [stdout(pipe(Out))]), |
63 | | - |
64 | | - get_n_chars(Out, N, _Chars, 2500), |
65 | | - |
66 | | - N >= 2, |
67 | | - N =< 3, |
68 | | - close(Out) |
| 75 | + iso_ext:setup_call_cleanup( |
| 76 | + process:process_create(Py, [C, Cmd], [stdout(pipe(Out))]), |
| 77 | + ( |
| 78 | + charsio:get_n_chars(Out, N, _Chars, 2500), |
| 79 | + N >= 2, |
| 80 | + N =< 3 |
| 81 | + ), |
| 82 | + close(Out) |
| 83 | + ) |
69 | 84 | )). |
70 | 85 |
|
71 | 86 | % Test 5: infinity atom means no timeout |
72 | 87 | test("infinity atom means no timeout", ( |
73 | 88 | atom_chars('/bin/echo', Echo), |
74 | 89 | atom_chars('InfinityTest', Content), |
75 | | - process_create(Echo, [Content], [stdout(pipe(Out))]), |
76 | | - |
77 | | - get_n_chars(Out, N, _Chars, infinity), |
78 | | - |
79 | | - N > 0, |
80 | | - close(Out) |
| 90 | + iso_ext:setup_call_cleanup( |
| 91 | + process:process_create(Echo, [Content], [stdout(pipe(Out))]), |
| 92 | + ( |
| 93 | + charsio:get_n_chars(Out, N, _Chars, infinity), |
| 94 | + N > 0 |
| 95 | + ), |
| 96 | + close(Out) |
| 97 | + ) |
81 | 98 | )). |
82 | 99 |
|
83 | 100 | % Test 6: Stream remains usable after timeout |
84 | 101 | test("stream usable after timeout", ( |
85 | 102 | atom_chars('/usr/bin/python3', Py), |
86 | 103 | atom_chars('-c', C), |
87 | 104 | atom_chars('import sys,time; print("A",end="",flush=True); time.sleep(2); print("B",end="",flush=True)', Cmd), |
88 | | - process_create(Py, [C, Cmd], [stdout(pipe(Out))]), |
89 | | - |
90 | | - get_n_chars(Out, N1, Chars1, 100), |
91 | | - get_n_chars(Out, N2, Chars2, 3000), |
92 | | - |
93 | | - N1 = 1, |
94 | | - Chars1 = "A", |
95 | | - N2 = 1, |
96 | | - Chars2 = "B", |
97 | | - close(Out) |
| 105 | + iso_ext:setup_call_cleanup( |
| 106 | + process:process_create(Py, [C, Cmd], [stdout(pipe(Out))]), |
| 107 | + ( |
| 108 | + charsio:get_n_chars(Out, N1, Chars1, 100), |
| 109 | + charsio:get_n_chars(Out, N2, Chars2, 3000), |
| 110 | + N1 = 1, |
| 111 | + Chars1 = "A", |
| 112 | + N2 = 1, |
| 113 | + Chars2 = "B" |
| 114 | + ), |
| 115 | + close(Out) |
| 116 | + ) |
98 | 117 | )). |
99 | 118 |
|
100 | 119 | % Test 7: Timeout returns partial data, not EOF |
101 | 120 | test("timeout returns partial data not EOF", ( |
102 | 121 | atom_chars('/usr/bin/python3', Py), |
103 | 122 | atom_chars('-c', C), |
104 | 123 | atom_chars('import sys,time; print("ABC",end="",flush=True); time.sleep(5); print("DEF",end="",flush=True)', Cmd), |
105 | | - process_create(Py, [C, Cmd], [stdout(pipe(Out))]), |
106 | | - |
107 | | - get_n_chars(Out, N1, Chars1, 1000), |
108 | | - get_n_chars(Out, N2, Chars2, 6000), |
109 | | - |
110 | | - N1 = 3, |
111 | | - Chars1 = "ABC", |
112 | | - N2 = 3, |
113 | | - Chars2 = "DEF", |
114 | | - close(Out) |
| 124 | + iso_ext:setup_call_cleanup( |
| 125 | + process:process_create(Py, [C, Cmd], [stdout(pipe(Out))]), |
| 126 | + ( |
| 127 | + charsio:get_n_chars(Out, N1, Chars1, 1000), |
| 128 | + charsio:get_n_chars(Out, N2, Chars2, 6000), |
| 129 | + N1 = 3, |
| 130 | + Chars1 = "ABC", |
| 131 | + N2 = 3, |
| 132 | + Chars2 = "DEF" |
| 133 | + ), |
| 134 | + close(Out) |
| 135 | + ) |
115 | 136 | )). |
116 | 137 |
|
117 | 138 | % Test 8: Multiple reads with timeout=0 |
118 | 139 | test("multiple reads with timeout=0", ( |
119 | 140 | atom_chars('/bin/echo', Echo), |
120 | 141 | atom_chars('ABCDEFGHIJKLMNOP', Content), |
121 | | - process_create(Echo, [Content], [stdout(pipe(Out))]), |
122 | | - |
123 | | - get_n_chars(Out, 4, Chars1, 0), |
124 | | - get_n_chars(Out, 4, Chars2, 0), |
125 | | - get_n_chars(Out, 4, Chars3, 0), |
126 | | - |
127 | | - Chars1 = "ABCD", |
128 | | - Chars2 = "EFGH", |
129 | | - Chars3 = "IJKL", |
130 | | - close(Out) |
| 142 | + iso_ext:setup_call_cleanup( |
| 143 | + process:process_create(Echo, [Content], [stdout(pipe(Out))]), |
| 144 | + ( |
| 145 | + charsio:get_n_chars(Out, 4, Chars1, 0), |
| 146 | + charsio:get_n_chars(Out, 4, Chars2, 0), |
| 147 | + charsio:get_n_chars(Out, 4, Chars3, 0), |
| 148 | + Chars1 = "ABCD", |
| 149 | + Chars2 = "EFGH", |
| 150 | + Chars3 = "IJKL" |
| 151 | + ), |
| 152 | + close(Out) |
| 153 | + ) |
131 | 154 | )). |
132 | 155 |
|
133 | 156 | % Test 9: Reading more than available with timeout=0 |
134 | 157 | test("read more than available with timeout=0", ( |
135 | 158 | atom_chars('/bin/echo', Echo), |
136 | 159 | atom_chars('Short', Content), |
137 | | - process_create(Echo, [Content], [stdout(pipe(Out))]), |
138 | | - |
139 | | - get_n_chars(Out, N, _Chars, 0), |
140 | | - |
141 | | - N >= 5, |
142 | | - N =< 7, |
143 | | - close(Out) |
| 160 | + iso_ext:setup_call_cleanup( |
| 161 | + process:process_create(Echo, [Content], [stdout(pipe(Out))]), |
| 162 | + ( |
| 163 | + charsio:get_n_chars(Out, N, _Chars, 0), |
| 164 | + N >= 5, |
| 165 | + N =< 7 |
| 166 | + ), |
| 167 | + close(Out) |
| 168 | + ) |
144 | 169 | )). |
145 | 170 |
|
146 | 171 | % Test 10: Variable N unifies with actual character count |
147 | 172 | test("variable N unifies with actual count", ( |
148 | 173 | atom_chars('/usr/bin/python3', Py), |
149 | 174 | atom_chars('-c', C), |
150 | 175 | atom_chars('import sys,time; [print(c,end="",flush=True) or time.sleep(0.5) for c in "ABCD"]', Cmd), |
151 | | - process_create(Py, [C, Cmd], [stdout(pipe(Out))]), |
152 | | - |
153 | | - get_n_chars(Out, N, Chars, 1300), |
154 | | - length(Chars, ActualLength), |
| 176 | + iso_ext:setup_call_cleanup( |
| 177 | + process:process_create(Py, [C, Cmd], [stdout(pipe(Out))]), |
| 178 | + ( |
| 179 | + charsio:get_n_chars(Out, N, Chars, 1300), |
| 180 | + lists:length(Chars, ActualLength), |
| 181 | + N = ActualLength |
| 182 | + ), |
| 183 | + close(Out) |
| 184 | + ) |
| 185 | +)). |
155 | 186 |
|
156 | | - N = ActualLength, |
157 | | - close(Out) |
| 187 | +% Test 11: UTF-8 multi-byte character boundaries with timeout |
| 188 | +% Ensures that partial UTF-8 sequences are preserved across timeouts |
| 189 | +test("utf8_multibyte_boundary_with_timeout", ( |
| 190 | + atom_chars('python3', Py), |
| 191 | + atom_chars('-c', C), |
| 192 | + % Send 💜 (F0 9F 92 9C) one byte at a time with delays |
| 193 | + atom_chars('import sys,time; sys.stdout.buffer.write(b\"\\xf0\"); sys.stdout.buffer.flush(); time.sleep(0.1); sys.stdout.buffer.write(b\"\\x9f\\x92\\x9c\"); sys.stdout.buffer.flush(); time.sleep(0.1); sys.stdout.buffer.write(b\"AB\"); sys.stdout.buffer.flush()', Cmd), |
| 194 | + iso_ext:setup_call_cleanup( |
| 195 | + process:process_create(Py, [C, Cmd], [stdout(pipe(Out))]), |
| 196 | + ( |
| 197 | + % First read: timeout after first byte (incomplete UTF-8) |
| 198 | + charsio:get_n_chars(Out, N1, _Chars1, 50), |
| 199 | + % Second read: should complete the emoji and get more |
| 200 | + charsio:get_n_chars(Out, N2, _Chars2, 500), |
| 201 | + % Verify lossless property: total should be 3 chars (💜 + A + B) |
| 202 | + TotalChars is N1 + N2, |
| 203 | + TotalChars = 3 |
| 204 | + ), |
| 205 | + close(Out) |
| 206 | + ) |
158 | 207 | )). |
0 commit comments