1+ """Tests for character I/O predicates (get_char/1-2, put_char/1-2)."""
2+
3+ import io
4+ import tempfile
5+ import pytest
6+ from vibeprolog import PrologInterpreter
7+ from vibeprolog .exceptions import PrologThrow
8+
9+
10+ @pytest .fixture
11+ def prolog () -> PrologInterpreter :
12+ return PrologInterpreter ()
13+
14+
15+ class TestGetChar :
16+ """Tests for get_char/1 and get_char/2 predicates."""
17+
18+ def test_get_char_from_string_stream (self , prolog : PrologInterpreter ):
19+ """Test reading characters from a string-based stream."""
20+ # Create a temporary file with test content
21+ with tempfile .NamedTemporaryFile (mode = 'w' , suffix = '.txt' , delete = False ) as f :
22+ f .write ("abc" )
23+ temp_file = f .name
24+
25+ try :
26+ # Open the file for reading
27+ open_result = prolog .query_once (f"open('{ temp_file } ', read, Stream)" )
28+ assert open_result is not None
29+ stream_handle = open_result ["Stream" ]
30+
31+ # Read first character
32+ result1 = prolog .query_once (f"get_char({ stream_handle } , Char1)" )
33+ assert result1 is not None
34+ assert result1 ["Char1" ] == "a"
35+
36+ # Read second character
37+ result2 = prolog .query_once (f"get_char({ stream_handle } , Char2)" )
38+ assert result2 is not None
39+ assert result2 ["Char2" ] == "b"
40+
41+ # Read third character
42+ result3 = prolog .query_once (f"get_char({ stream_handle } , Char3)" )
43+ assert result3 is not None
44+ assert result3 ["Char3" ] == "c"
45+
46+ # Read EOF
47+ result4 = prolog .query_once (f"get_char({ stream_handle } , Char4)" )
48+ assert result4 is not None
49+ assert result4 ["Char4" ] == "end_of_file"
50+
51+ # Close stream
52+ prolog .query_once (f"close({ stream_handle } )" )
53+ finally :
54+ import os
55+ os .unlink (temp_file )
56+
57+ def test_get_char_current_input_fails (self , prolog : PrologInterpreter ):
58+ """Test that get_char/1 fails on current input (stdin) with permission_error."""
59+ # This should fail because stdin is captured by pytest
60+ result = prolog .query_once ("catch(get_char(Char), Error, true)" )
61+ assert result is not None
62+ error_term = result ["Error" ]
63+ assert "error" in error_term
64+ permission_error = error_term ["error" ][0 ]
65+ assert "permission_error" in permission_error
66+ args = permission_error ["permission_error" ]
67+ assert args [0 ] == "input"
68+ assert args [1 ] == "stream"
69+
70+ def test_get_char_invalid_stream (self , prolog : PrologInterpreter ):
71+ """Test get_char/2 with invalid stream raises existence_error."""
72+ result = prolog .query_once ("catch(get_char(invalid_stream, Char), Error, true)" )
73+ assert result is not None
74+ error_term = result ["Error" ]
75+ assert "error" in error_term
76+ existence_error = error_term ["error" ][0 ]
77+ assert "existence_error" in existence_error
78+ args = existence_error ["existence_error" ]
79+ assert args [0 ] == "stream"
80+
81+ def test_get_char_write_only_stream (self , prolog : PrologInterpreter ):
82+ """Test get_char on write-only stream raises permission_error."""
83+ with tempfile .NamedTemporaryFile (mode = 'w' , suffix = '.txt' , delete = False ) as f :
84+ temp_file = f .name
85+
86+ try :
87+ # Open for writing
88+ open_result = prolog .query_once (f"open('{ temp_file } ', write, Stream)" )
89+ assert open_result is not None
90+ stream_handle = open_result ["Stream" ]
91+
92+ # Try to read from write-only stream
93+ result = prolog .query_once (f"catch(get_char({ stream_handle } , Char), Error, true)" )
94+ assert result is not None
95+ error_term = result ["Error" ]
96+ assert "error" in error_term
97+ permission_error = error_term ["error" ][0 ]
98+ assert "permission_error" in permission_error
99+ args = permission_error ["permission_error" ]
100+ assert args [0 ] == "input"
101+ assert args [1 ] == "stream"
102+
103+ prolog .query_once (f"close({ stream_handle } )" )
104+ finally :
105+ import os
106+ os .unlink (temp_file )
107+
108+
109+ class TestPutChar :
110+ """Tests for put_char/1 and put_char/2 predicates."""
111+
112+ def test_put_char_to_file (self , prolog : PrologInterpreter ):
113+ """Test writing characters to a file stream."""
114+ with tempfile .NamedTemporaryFile (mode = 'w' , suffix = '.txt' , delete = False ) as f :
115+ temp_file = f .name
116+
117+ try :
118+ # Open the file for writing
119+ open_result = prolog .query_once (f"open('{ temp_file } ', write, Stream)" )
120+ assert open_result is not None
121+ stream_handle = open_result ["Stream" ]
122+
123+ # Write characters
124+ assert prolog .query_once (f"put_char({ stream_handle } , 'H')" ) == {}
125+ assert prolog .query_once (f"put_char({ stream_handle } , 'e')" ) == {}
126+ assert prolog .query_once (f"put_char({ stream_handle } , 'l')" ) == {}
127+ assert prolog .query_once (f"put_char({ stream_handle } , 'l')" ) == {}
128+ assert prolog .query_once (f"put_char({ stream_handle } , 'o')" ) == {}
129+
130+ # Close stream
131+ prolog .query_once (f"close({ stream_handle } )" )
132+
133+ # Verify content
134+ with open (temp_file , 'r' ) as f :
135+ content = f .read ()
136+ assert content == "Hello"
137+ finally :
138+ import os
139+ os .unlink (temp_file )
140+
141+ def test_put_char_invalid_character (self , prolog : PrologInterpreter ):
142+ """Test put_char with invalid character raises type_error."""
143+ with tempfile .NamedTemporaryFile (mode = 'w' , suffix = '.txt' , delete = False ) as f :
144+ temp_file = f .name
145+
146+ try :
147+ open_result = prolog .query_once (f"open('{ temp_file } ', write, Stream)" )
148+ assert open_result is not None
149+ stream_handle = open_result ["Stream" ]
150+
151+ # Try to write multi-character atom
152+ result = prolog .query_once (f"catch(put_char({ stream_handle } , 'ab'), Error, true)" )
153+ assert result is not None
154+ error_term = result ["Error" ]
155+ assert "error" in error_term
156+ type_error = error_term ["error" ][0 ]
157+ assert "type_error" in type_error
158+ args = type_error ["type_error" ]
159+ assert args [0 ] == "in_character"
160+
161+ # Try to write non-atom
162+ result2 = prolog .query_once (f"catch(put_char({ stream_handle } , 123), Error2, true)" )
163+ assert result2 is not None
164+ error_term2 = result2 ["Error2" ]
165+ assert "error" in error_term2
166+ type_error2 = error_term2 ["error" ][0 ]
167+ assert "type_error" in type_error2
168+
169+ prolog .query_once (f"close({ stream_handle } )" )
170+ finally :
171+ import os
172+ os .unlink (temp_file )
173+
174+ def test_put_char_invalid_stream (self , prolog : PrologInterpreter ):
175+ """Test put_char/2 with invalid stream raises existence_error."""
176+ result = prolog .query_once ("catch(put_char(invalid_stream, 'a'), Error, true)" )
177+ assert result is not None
178+ error_term = result ["Error" ]
179+ assert "error" in error_term
180+ existence_error = error_term ["error" ][0 ]
181+ assert "existence_error" in existence_error
182+ args = existence_error ["existence_error" ]
183+ assert args [0 ] == "stream"
184+
185+ def test_put_char_read_only_stream (self , prolog : PrologInterpreter ):
186+ """Test put_char on read-only stream raises permission_error."""
187+ with tempfile .NamedTemporaryFile (mode = 'w' , suffix = '.txt' , delete = False ) as f :
188+ f .write ("test" )
189+ temp_file = f .name
190+
191+ try :
192+ # Open for reading
193+ open_result = prolog .query_once (f"open('{ temp_file } ', read, Stream)" )
194+ assert open_result is not None
195+ stream_handle = open_result ["Stream" ]
196+
197+ # Try to write to read-only stream
198+ result = prolog .query_once (f"catch(put_char({ stream_handle } , 'a'), Error, true)" )
199+ assert result is not None
200+ error_term = result ["Error" ]
201+ assert "error" in error_term
202+ permission_error = error_term ["error" ][0 ]
203+ assert "permission_error" in permission_error
204+ args = permission_error ["permission_error" ]
205+ assert args [0 ] == "output"
206+ assert args [1 ] == "stream"
207+
208+ prolog .query_once (f"close({ stream_handle } )" )
209+ finally :
210+ import os
211+ os .unlink (temp_file )
212+
213+ def test_put_char_uninstantiated_args (self , prolog : PrologInterpreter ):
214+ """Test put_char with uninstantiated arguments raises instantiation_error."""
215+ with tempfile .NamedTemporaryFile (mode = 'w' , suffix = '.txt' , delete = False ) as f :
216+ temp_file = f .name
217+
218+ try :
219+ open_result = prolog .query_once (f"open('{ temp_file } ', write, Stream)" )
220+ assert open_result is not None
221+ stream_handle = open_result ["Stream" ]
222+
223+ # Uninstantiated character
224+ result = prolog .query_once (f"catch(put_char({ stream_handle } , Char), Error, true)" )
225+ assert result is not None
226+ error_term = result ["Error" ]
227+ assert "error" in error_term
228+ instantiation_error = error_term ["error" ][0 ]
229+ assert "instantiation_error" in instantiation_error
230+
231+ # Uninstantiated stream
232+ result2 = prolog .query_once ("catch(put_char(Stream, 'a'), Error2, true)" )
233+ assert result2 is not None
234+ error_term2 = result2 ["Error2" ]
235+ assert "error" in error_term2
236+ instantiation_error2 = error_term2 ["error" ][0 ]
237+ assert "instantiation_error" in instantiation_error2
238+
239+ prolog .query_once (f"close({ stream_handle } )" )
240+ finally :
241+ import os
242+ os .unlink (temp_file )
243+
244+
245+ class TestCharIOEdgeCases :
246+ """Tests for edge cases in character I/O."""
247+
248+ def test_get_char_newline (self , prolog : PrologInterpreter ):
249+ """Test reading newline character."""
250+ with tempfile .NamedTemporaryFile (mode = 'w' , suffix = '.txt' , delete = False ) as f :
251+ f .write ("a\n b" )
252+ temp_file = f .name
253+
254+ try :
255+ open_result = prolog .query_once (f"open('{ temp_file } ', read, Stream)" )
256+ assert open_result is not None
257+ stream_handle = open_result ["Stream" ]
258+
259+ result1 = prolog .query_once (f"get_char({ stream_handle } , Char1)" )
260+ assert result1 ["Char1" ] == "a"
261+
262+ result2 = prolog .query_once (f"get_char({ stream_handle } , Char2)" )
263+ assert result2 ["Char2" ] == "\n "
264+
265+ result3 = prolog .query_once (f"get_char({ stream_handle } , Char3)" )
266+ assert result3 ["Char3" ] == "b"
267+
268+ prolog .query_once (f"close({ stream_handle } )" )
269+ finally :
270+ import os
271+ os .unlink (temp_file )
272+
273+ def test_put_char_special_chars (self , prolog : PrologInterpreter ):
274+ """Test writing special characters."""
275+ with tempfile .NamedTemporaryFile (mode = 'w' , suffix = '.txt' , delete = False ) as f :
276+ temp_file = f .name
277+
278+ try :
279+ open_result = prolog .query_once (f"open('{ temp_file } ', write, Stream)" )
280+ assert open_result is not None
281+ stream_handle = open_result ["Stream" ]
282+
283+ # Write various special characters
284+ assert prolog .query_once (f"put_char({ stream_handle } , '\\ n')" ) == {}
285+ assert prolog .query_once (f"put_char({ stream_handle } , '\\ t')" ) == {}
286+ assert prolog .query_once (f"put_char({ stream_handle } , ' ')" ) == {}
287+
288+ prolog .query_once (f"close({ stream_handle } )" )
289+
290+ with open (temp_file , 'r' ) as f :
291+ content = f .read ()
292+ assert content == "\n \t "
293+ finally :
294+ import os
295+ os .unlink (temp_file )
0 commit comments