Skip to content

Commit c16dec0

Browse files
committed
Apply setup_call_cleanup pattern with module qualification
Apply setup_call_cleanup/3 to all test cases for reliable resource cleanup, with module qualifications to work around testing framework idiosyncracy. Changes: - Wrap all process_create calls with iso_ext:setup_call_cleanup - Add library(iso_ext) import to test files - Qualify all imported predicates with their module names: - setup_call_cleanup with iso_ext: - process_create with process: - get_n_chars, get_char, peek_char, get_code, peek_code, and get_line_to_chars with charsio: - length with lists:
1 parent f43f85c commit c16dec0

File tree

2 files changed

+227
-145
lines changed

2 files changed

+227
-145
lines changed

src/tests/get_n_chars.pl

Lines changed: 136 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
:- module(get_n_chars_tests, []).
22
:- use_module(test_framework).
33
:- use_module(library(charsio)).
4+
:- use_module(library(iso_ext)).
45
:- use_module(library(process)).
56
:- use_module(library(format)).
67
:- use_module(library(lists)).
@@ -9,150 +10,198 @@
910
test("timeout=0 equals get_n_chars/3", (
1011
atom_chars('/bin/echo', Echo),
1112
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+
)
2126
)).
2227

2328
% Test 2: Variable N with timeout=0
2429
test("variable N with timeout=0", (
2530
atom_chars('/bin/echo', Echo),
2631
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+
)
3948
)).
4049

4150
% Test 3: Negative timeout should also mean no timeout
4251
test("negative timeout equals no timeout", (
4352
atom_chars('/bin/echo', Echo),
4453
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+
)
5568
)).
5669

5770
% Test 4: Positive timeout should timeout on slow output
5871
test("positive timeout stops reading", (
5972
atom_chars('/usr/bin/python3', Py),
6073
atom_chars('-c', C),
6174
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+
)
6984
)).
7085

7186
% Test 5: infinity atom means no timeout
7287
test("infinity atom means no timeout", (
7388
atom_chars('/bin/echo', Echo),
7489
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+
)
8198
)).
8299

83100
% Test 6: Stream remains usable after timeout
84101
test("stream usable after timeout", (
85102
atom_chars('/usr/bin/python3', Py),
86103
atom_chars('-c', C),
87104
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+
)
98117
)).
99118

100119
% Test 7: Timeout returns partial data, not EOF
101120
test("timeout returns partial data not EOF", (
102121
atom_chars('/usr/bin/python3', Py),
103122
atom_chars('-c', C),
104123
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+
)
115136
)).
116137

117138
% Test 8: Multiple reads with timeout=0
118139
test("multiple reads with timeout=0", (
119140
atom_chars('/bin/echo', Echo),
120141
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+
)
131154
)).
132155

133156
% Test 9: Reading more than available with timeout=0
134157
test("read more than available with timeout=0", (
135158
atom_chars('/bin/echo', Echo),
136159
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+
)
144169
)).
145170

146171
% Test 10: Variable N unifies with actual character count
147172
test("variable N unifies with actual count", (
148173
atom_chars('/usr/bin/python3', Py),
149174
atom_chars('-c', C),
150175
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+
)).
155186

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+
)
158207
)).

0 commit comments

Comments
 (0)