xref: /llvm-project/lldb/test/API/python_api/file_handle/TestFileHandle.py (revision 9c2468821ec51defd09c246fea4a47886fff8c01)
1"""
2Test lldb Python API for file handles.
3"""
4
5
6import os
7import io
8import re
9import sys
10from contextlib import contextmanager
11
12import lldb
13from lldbsuite.test import lldbtest
14from lldbsuite.test.decorators import *
15
16
17class OhNoe(Exception):
18    pass
19
20
21class BadIO(io.TextIOBase):
22    @property
23    def closed(self):
24        return False
25
26    def writable(self):
27        return True
28
29    def readable(self):
30        return True
31
32    def write(self, s):
33        raise OhNoe("OH NOE")
34
35    def read(self, n):
36        raise OhNoe("OH NOE")
37
38    def flush(self):
39        raise OhNoe("OH NOE")
40
41
42# This class will raise an exception while it's being
43# converted into a C++ object by swig
44class ReallyBadIO(io.TextIOBase):
45    def fileno(self):
46        return 999
47
48    def writable(self):
49        raise OhNoe("OH NOE!!!")
50
51
52class MutableBool:
53    def __init__(self, value):
54        self.value = value
55
56    def set(self, value):
57        self.value = bool(value)
58
59    def __bool__(self):
60        return self.value
61
62
63class FlushTestIO(io.StringIO):
64    def __init__(self, mutable_flushed, mutable_closed):
65        super(FlushTestIO, self).__init__()
66        self.mut_flushed = mutable_flushed
67        self.mut_closed = mutable_closed
68
69    def close(self):
70        self.mut_closed.set(True)
71        return super(FlushTestIO, self).close()
72
73    def flush(self):
74        self.mut_flushed.set(True)
75        return super(FlushTestIO, self).flush()
76
77
78@contextmanager
79def replace_stdout(new):
80    old = sys.stdout
81    sys.stdout = new
82    try:
83        yield
84    finally:
85        sys.stdout = old
86
87
88def readStrippedLines(f):
89    def i():
90        for line in f:
91            line = line.strip()
92            if line:
93                yield line
94
95    return list(i())
96
97
98class FileHandleTestCase(lldbtest.TestBase):
99    NO_DEBUG_INFO_TESTCASE = True
100
101    # The way normal tests evaluate debugger commands is
102    # by using a SBCommandInterpreter directly, which captures
103    # the output in a result object.   For many of tests tests
104    # we want the debugger to write the  output directly to
105    # its I/O streams like it would have done interactively.
106    #
107    # For this reason we also define handleCmd() here, even though
108    # it is similar to runCmd().
109
110    def setUp(self):
111        super(FileHandleTestCase, self).setUp()
112        self.out_filename = self.getBuildArtifact("output")
113        self.in_filename = self.getBuildArtifact("input")
114
115    def tearDown(self):
116        super(FileHandleTestCase, self).tearDown()
117        for name in (self.out_filename, self.in_filename):
118            if os.path.exists(name):
119                os.unlink(name)
120
121    # Similar to runCmd(), but letting the debugger just print the results
122    # instead of collecting them.
123    def handleCmd(self, cmd, check=True, collect_result=True):
124        assert not check or collect_result
125        ret = lldb.SBCommandReturnObject()
126        if collect_result:
127            interpreter = self.dbg.GetCommandInterpreter()
128            interpreter.HandleCommand(cmd, ret)
129        else:
130            self.dbg.HandleCommand(cmd)
131        self.dbg.GetOutputFile().Flush()
132        self.dbg.GetErrorFile().Flush()
133        if collect_result and check:
134            self.assertTrue(ret.Succeeded())
135        return ret.GetOutput()
136
137    def test_legacy_file_out_script(self):
138        with open(self.out_filename, "w") as f:
139            self.dbg.SetOutputFileHandle(f, False)
140            # scripts print to output even if you capture the results
141            # I'm not sure I love that behavior, but that's the way
142            # it's been for a long time.  That's why this test works
143            # even with collect_result=True.
144            self.handleCmd("script 1+1")
145            self.dbg.GetOutputFileHandle().write("FOO\n")
146            self.dbg.GetOutputFileHandle().flush()
147        with open(self.out_filename, "r") as f:
148            self.assertEqual(readStrippedLines(f), ["2", "FOO"])
149
150    def test_legacy_file_out(self):
151        with open(self.out_filename, "w") as f:
152            self.dbg.SetOutputFileHandle(f, False)
153            self.handleCmd("expression/x 3735928559", collect_result=False, check=False)
154        with open(self.out_filename, "r") as f:
155            self.assertIn("deadbeef", f.read())
156
157    def test_legacy_file_err_with_get(self):
158        with open(self.out_filename, "w") as f:
159            self.dbg.SetErrorFileHandle(f, False)
160            self.handleCmd("lolwut", check=False, collect_result=False)
161            f2 = self.dbg.GetErrorFileHandle()
162            f2.write("FOOBAR\n")
163            f2.flush()
164        with open(self.out_filename, "r") as f:
165            errors = f.read()
166            self.assertTrue(re.search(r"error:.*lolwut", errors))
167            self.assertTrue(re.search(r"FOOBAR", errors))
168
169    def test_legacy_file_err(self):
170        with open(self.out_filename, "w") as f:
171            self.dbg.SetErrorFileHandle(f, False)
172            self.handleCmd("lol", check=False, collect_result=False)
173        with open(self.out_filename, "r") as f:
174            self.assertIn("is not a valid command", f.read())
175
176    def test_legacy_file_error(self):
177        with open(self.out_filename, "w") as f:
178            self.dbg.SetErrorFileHandle(f, False)
179            self.handleCmd("lolwut", check=False, collect_result=False)
180        with open(self.out_filename, "r") as f:
181            errors = f.read()
182            self.assertTrue(re.search(r"error:.*lolwut", errors))
183
184    def test_sbfile_type_errors(self):
185        sbf = lldb.SBFile()
186        self.assertRaises(Exception, sbf.Write, None)
187        self.assertRaises(Exception, sbf.Read, None)
188        self.assertRaises(Exception, sbf.Read, b"this bytes is not mutable")
189        self.assertRaises(Exception, sbf.Write, "ham sandwich")
190        self.assertRaises(Exception, sbf.Read, "ham sandwich")
191
192    def test_sbfile_write_fileno(self):
193        with open(self.out_filename, "w") as f:
194            sbf = lldb.SBFile(f.fileno(), "w", False)
195            self.assertTrue(sbf.IsValid())
196            e, n = sbf.Write(b"FOO\nBAR")
197            self.assertSuccess(e)
198            self.assertEqual(n, 7)
199            sbf.Close()
200            self.assertFalse(sbf.IsValid())
201        with open(self.out_filename, "r") as f:
202            self.assertEqual(readStrippedLines(f), ["FOO", "BAR"])
203
204    def test_sbfile_write(self):
205        with open(self.out_filename, "w") as f:
206            sbf = lldb.SBFile(f)
207            e, n = sbf.Write(b"FOO\n")
208            self.assertSuccess(e)
209            self.assertEqual(n, 4)
210            sbf.Close()
211            self.assertTrue(f.closed)
212        with open(self.out_filename, "r") as f:
213            self.assertEqual(f.read().strip(), "FOO")
214
215    def test_sbfile_read_fileno(self):
216        with open(self.out_filename, "w") as f:
217            f.write("FOO")
218        with open(self.out_filename, "r") as f:
219            sbf = lldb.SBFile(f.fileno(), "r", False)
220            self.assertTrue(sbf.IsValid())
221            buffer = bytearray(100)
222            e, n = sbf.Read(buffer)
223            self.assertSuccess(e)
224            self.assertEqual(buffer[:n], b"FOO")
225
226    def test_sbfile_read(self):
227        with open(self.out_filename, "w") as f:
228            f.write("foo")
229        with open(self.out_filename, "r") as f:
230            sbf = lldb.SBFile(f)
231            buf = bytearray(100)
232            e, n = sbf.Read(buf)
233            self.assertSuccess(e)
234            self.assertEqual(n, 3)
235            self.assertEqual(buf[:n], b"foo")
236            sbf.Close()
237            self.assertTrue(f.closed)
238
239    def test_fileno_out(self):
240        with open(self.out_filename, "w") as f:
241            sbf = lldb.SBFile(f.fileno(), "w", False)
242            status = self.dbg.SetOutputFile(sbf)
243            self.assertSuccess(status)
244            self.handleCmd("script 1+2")
245            self.dbg.GetOutputFile().Write(b"quux")
246            self.dbg.GetOutputFile().Flush()
247
248        with open(self.out_filename, "r") as f:
249            self.assertEqual(readStrippedLines(f), ["3", "quux"])
250
251    def test_fileno_help(self):
252        with open(self.out_filename, "w") as f:
253            sbf = lldb.SBFile(f.fileno(), "w", False)
254            status = self.dbg.SetOutputFile(sbf)
255            self.assertSuccess(status)
256            self.handleCmd("help help", collect_result=False, check=False)
257        with open(self.out_filename, "r") as f:
258            self.assertTrue(
259                re.search(r"Show a list of all debugger commands", f.read())
260            )
261
262    def test_help(self):
263        with open(self.out_filename, "w") as f:
264            status = self.dbg.SetOutputFile(lldb.SBFile(f))
265            self.assertSuccess(status)
266            self.handleCmd("help help", check=False, collect_result=False)
267        with open(self.out_filename, "r") as f:
268            self.assertIn("Show a list of all debugger commands", f.read())
269
270    def test_immediate(self):
271        with open(self.out_filename, "w") as f:
272            ret = lldb.SBCommandReturnObject()
273            ret.SetImmediateOutputFile(f)
274            interpreter = self.dbg.GetCommandInterpreter()
275            interpreter.HandleCommand("help help", ret)
276            # make sure the file wasn't closed early.
277            f.write("\nQUUX\n")
278        ret = None  # call destructor and flush streams
279        with open(self.out_filename, "r") as f:
280            output = f.read()
281            self.assertTrue(re.search(r"Show a list of all debugger commands", output))
282            self.assertTrue(re.search(r"QUUX", output))
283
284    def test_immediate_string(self):
285        f = io.StringIO()
286        ret = lldb.SBCommandReturnObject()
287        ret.SetImmediateOutputFile(f)
288        interpreter = self.dbg.GetCommandInterpreter()
289        interpreter.HandleCommand("help help", ret)
290        # make sure the file wasn't closed early.
291        f.write("\nQUUX\n")
292        ret = None  # call destructor and flush streams
293        output = f.getvalue()
294        self.assertTrue(re.search(r"Show a list of all debugger commands", output))
295        self.assertTrue(re.search(r"QUUX", output))
296
297    def test_immediate_sbfile_string(self):
298        f = io.StringIO()
299        ret = lldb.SBCommandReturnObject()
300        ret.SetImmediateOutputFile(lldb.SBFile(f))
301        interpreter = self.dbg.GetCommandInterpreter()
302        interpreter.HandleCommand("help help", ret)
303        output = f.getvalue()
304        ret = None  # call destructor and flush streams
305        # sbfile default constructor doesn't borrow the file
306        self.assertTrue(f.closed)
307        self.assertTrue(re.search(r"Show a list of all debugger commands", output))
308
309    def test_fileno_inout(self):
310        with open(self.in_filename, "w") as f:
311            f.write("help help\n")
312
313        with open(self.out_filename, "w") as outf, open(self.in_filename, "r") as inf:
314            outsbf = lldb.SBFile(outf.fileno(), "w", False)
315            status = self.dbg.SetOutputFile(outsbf)
316            self.assertSuccess(status)
317
318            insbf = lldb.SBFile(inf.fileno(), "r", False)
319            status = self.dbg.SetInputFile(insbf)
320            self.assertSuccess(status)
321
322            opts = lldb.SBCommandInterpreterRunOptions()
323            self.dbg.RunCommandInterpreter(True, False, opts, 0, False, False)
324            self.dbg.GetOutputFile().Flush()
325
326        with open(self.out_filename, "r") as f:
327            self.assertTrue(
328                re.search(r"Show a list of all debugger commands", f.read())
329            )
330
331    def test_inout(self):
332        with open(self.in_filename, "w") as f:
333            f.write("help help\n")
334        with open(self.out_filename, "w") as outf, open(self.in_filename, "r") as inf:
335            status = self.dbg.SetOutputFile(lldb.SBFile(outf))
336            self.assertSuccess(status)
337            status = self.dbg.SetInputFile(lldb.SBFile(inf))
338            self.assertSuccess(status)
339            opts = lldb.SBCommandInterpreterRunOptions()
340            self.dbg.RunCommandInterpreter(True, False, opts, 0, False, False)
341            self.dbg.GetOutputFile().Flush()
342        with open(self.out_filename, "r") as f:
343            output = f.read()
344            self.assertIn("Show a list of all debugger commands", output)
345
346    def test_binary_inout(self):
347        with open(self.in_filename, "w") as f:
348            f.write("help help\n")
349        with open(self.out_filename, "wb") as outf, open(self.in_filename, "rb") as inf:
350            status = self.dbg.SetOutputFile(lldb.SBFile(outf))
351            self.assertSuccess(status)
352            status = self.dbg.SetInputFile(lldb.SBFile(inf))
353            self.assertSuccess(status)
354            opts = lldb.SBCommandInterpreterRunOptions()
355            self.dbg.RunCommandInterpreter(True, False, opts, 0, False, False)
356            self.dbg.GetOutputFile().Flush()
357        with open(self.out_filename, "r") as f:
358            output = f.read()
359            self.assertIn("Show a list of all debugger commands", output)
360
361    def test_string_inout(self):
362        inf = io.StringIO("help help\nexpression/x ~0\n")
363        outf = io.StringIO()
364        status = self.dbg.SetOutputFile(lldb.SBFile(outf))
365        self.assertSuccess(status)
366        status = self.dbg.SetInputFile(lldb.SBFile(inf))
367        self.assertSuccess(status)
368        opts = lldb.SBCommandInterpreterRunOptions()
369        self.dbg.RunCommandInterpreter(True, False, opts, 0, False, False)
370        self.dbg.GetOutputFile().Flush()
371        output = outf.getvalue()
372        self.assertIn("Show a list of all debugger commands", output)
373        self.assertIn("0xfff", output)
374
375    def test_bytes_inout(self):
376        inf = io.BytesIO(b"help help\nhelp b\n")
377        outf = io.BytesIO()
378        status = self.dbg.SetOutputFile(lldb.SBFile(outf))
379        self.assertSuccess(status)
380        status = self.dbg.SetInputFile(lldb.SBFile(inf))
381        self.assertSuccess(status)
382        opts = lldb.SBCommandInterpreterRunOptions()
383        self.dbg.RunCommandInterpreter(True, False, opts, 0, False, False)
384        self.dbg.GetOutputFile().Flush()
385        output = outf.getvalue()
386        self.assertIn(b"Show a list of all debugger commands", output)
387        self.assertIn(b"Set a breakpoint", output)
388
389    def test_fileno_error(self):
390        with open(self.out_filename, "w") as f:
391            sbf = lldb.SBFile(f.fileno(), "w", False)
392            status = self.dbg.SetErrorFile(sbf)
393            self.assertSuccess(status)
394
395            self.handleCmd("lolwut", check=False, collect_result=False)
396
397            self.dbg.GetErrorFile().Write(b"\nzork\n")
398
399        with open(self.out_filename, "r") as f:
400            errors = f.read()
401            self.assertTrue(re.search(r"error:.*lolwut", errors))
402            self.assertTrue(re.search(r"zork", errors))
403
404    def test_replace_stdout(self):
405        f = io.StringIO()
406        with replace_stdout(f):
407            self.assertEqual(sys.stdout, f)
408            self.handleCmd(
409                'script sys.stdout.write("lol")', collect_result=False, check=False
410            )
411            self.assertEqual(sys.stdout, f)
412
413    def test_replace_stdout_with_nonfile(self):
414        f = io.StringIO()
415        with replace_stdout(f):
416
417            class Nothing:
418                pass
419
420            with replace_stdout(Nothing):
421                self.assertEqual(sys.stdout, Nothing)
422                self.handleCmd(
423                    'script sys.stdout.write("lol")', check=False, collect_result=False
424                )
425                self.assertEqual(sys.stdout, Nothing)
426            sys.stdout.write("FOO")
427        self.assertEqual(f.getvalue(), "FOO")
428
429    def test_sbfile_write_borrowed(self):
430        with open(self.out_filename, "w") as f:
431            sbf = lldb.SBFile.Create(f, borrow=True)
432            e, n = sbf.Write(b"FOO")
433            self.assertSuccess(e)
434            self.assertEqual(n, 3)
435            sbf.Close()
436            self.assertFalse(f.closed)
437            f.write("BAR\n")
438        with open(self.out_filename, "r") as f:
439            self.assertEqual(f.read().strip(), "FOOBAR")
440
441    def test_sbfile_write_forced(self):
442        with open(self.out_filename, "w") as f:
443            written = MutableBool(False)
444            orig_write = f.write
445
446            def mywrite(x):
447                written.set(True)
448                return orig_write(x)
449
450            f.write = mywrite
451            sbf = lldb.SBFile.Create(f, force_io_methods=True)
452            e, n = sbf.Write(b"FOO")
453            self.assertTrue(written)
454            self.assertSuccess(e)
455            self.assertEqual(n, 3)
456            sbf.Close()
457            self.assertTrue(f.closed)
458        with open(self.out_filename, "r") as f:
459            self.assertEqual(f.read().strip(), "FOO")
460
461    def test_sbfile_write_forced_borrowed(self):
462        with open(self.out_filename, "w") as f:
463            written = MutableBool(False)
464            orig_write = f.write
465
466            def mywrite(x):
467                written.set(True)
468                return orig_write(x)
469
470            f.write = mywrite
471            sbf = lldb.SBFile.Create(f, borrow=True, force_io_methods=True)
472            e, n = sbf.Write(b"FOO")
473            self.assertTrue(written)
474            self.assertSuccess(e)
475            self.assertEqual(n, 3)
476            sbf.Close()
477            self.assertFalse(f.closed)
478        with open(self.out_filename, "r") as f:
479            self.assertEqual(f.read().strip(), "FOO")
480
481    def test_sbfile_write_string(self):
482        f = io.StringIO()
483        sbf = lldb.SBFile(f)
484        e, n = sbf.Write(b"FOO")
485        self.assertEqual(f.getvalue().strip(), "FOO")
486        self.assertSuccess(e)
487        self.assertEqual(n, 3)
488        sbf.Close()
489        self.assertTrue(f.closed)
490
491    def test_string_out(self):
492        f = io.StringIO()
493        status = self.dbg.SetOutputFile(f)
494        self.assertSuccess(status)
495        self.handleCmd("script 'foobar'")
496        self.assertEqual(f.getvalue().strip(), "'foobar'")
497
498    def test_string_error(self):
499        f = io.StringIO()
500        status = self.dbg.SetErrorFile(f)
501        self.assertSuccess(status)
502        self.handleCmd("lolwut", check=False, collect_result=False)
503        errors = f.getvalue()
504        self.assertTrue(re.search(r"error:.*lolwut", errors))
505
506    def test_sbfile_write_bytes(self):
507        f = io.BytesIO()
508        sbf = lldb.SBFile(f)
509        e, n = sbf.Write(b"FOO")
510        self.assertEqual(f.getvalue().strip(), b"FOO")
511        self.assertSuccess(e)
512        self.assertEqual(n, 3)
513        sbf.Close()
514        self.assertTrue(f.closed)
515
516    def test_sbfile_read_string(self):
517        f = io.StringIO("zork")
518        sbf = lldb.SBFile(f)
519        buf = bytearray(100)
520        e, n = sbf.Read(buf)
521        self.assertSuccess(e)
522        self.assertEqual(buf[:n], b"zork")
523
524    def test_sbfile_read_string_one_byte(self):
525        f = io.StringIO("z")
526        sbf = lldb.SBFile(f)
527        buf = bytearray(1)
528        e, n = sbf.Read(buf)
529        self.assertTrue(e.Fail())
530        self.assertEqual(n, 0)
531        self.assertEqual(
532            e.GetCString(), "can't read less than 6 bytes from a utf8 text stream"
533        )
534
535    def test_sbfile_read_bytes(self):
536        f = io.BytesIO(b"zork")
537        sbf = lldb.SBFile(f)
538        buf = bytearray(100)
539        e, n = sbf.Read(buf)
540        self.assertSuccess(e)
541        self.assertEqual(buf[:n], b"zork")
542
543    def test_sbfile_out(self):
544        with open(self.out_filename, "w") as f:
545            sbf = lldb.SBFile(f)
546            status = self.dbg.SetOutputFile(sbf)
547            self.assertSuccess(status)
548            self.handleCmd("script 2+2")
549        with open(self.out_filename, "r") as f:
550            self.assertEqual(f.read().strip(), "4")
551
552    def test_file_out(self):
553        with open(self.out_filename, "w") as f:
554            status = self.dbg.SetOutputFile(f)
555            self.assertSuccess(status)
556            self.handleCmd("script 2+2")
557        with open(self.out_filename, "r") as f:
558            self.assertEqual(f.read().strip(), "4")
559
560    def test_sbfile_error(self):
561        with open(self.out_filename, "w") as f:
562            sbf = lldb.SBFile(f)
563            status = self.dbg.SetErrorFile(sbf)
564            self.assertSuccess(status)
565            self.handleCmd("lolwut", check=False, collect_result=False)
566        with open(self.out_filename, "r") as f:
567            errors = f.read()
568            self.assertTrue(re.search(r"error:.*lolwut", errors))
569
570    def test_file_error(self):
571        with open(self.out_filename, "w") as f:
572            status = self.dbg.SetErrorFile(f)
573            self.assertSuccess(status)
574            self.handleCmd("lolwut", check=False, collect_result=False)
575        with open(self.out_filename, "r") as f:
576            errors = f.read()
577            self.assertTrue(re.search(r"error:.*lolwut", errors))
578
579    def test_exceptions(self):
580        self.assertRaises(Exception, lldb.SBFile, None)
581        self.assertRaises(Exception, lldb.SBFile, "ham sandwich")
582        self.assertRaises(OhNoe, lldb.SBFile, ReallyBadIO())
583        error, n = lldb.SBFile(BadIO()).Write(b"FOO")
584        self.assertEqual(n, 0)
585        self.assertTrue(error.Fail())
586        self.assertIn("OH NOE", error.GetCString())
587        error, n = lldb.SBFile(BadIO()).Read(bytearray(100))
588        self.assertEqual(n, 0)
589        self.assertTrue(error.Fail())
590        self.assertIn("OH NOE", error.GetCString())
591
592    def test_exceptions_logged(self):
593        messages = list()
594        self.dbg.SetLoggingCallback(messages.append)
595        self.handleCmd("log enable lldb script")
596        self.dbg.SetOutputFile(lldb.SBFile(BadIO()))
597        self.handleCmd("script 1+1")
598        self.assertTrue(any("OH NOE" in msg for msg in messages))
599
600    def test_flush(self):
601        flushed = MutableBool(False)
602        closed = MutableBool(False)
603        f = FlushTestIO(flushed, closed)
604        self.assertFalse(flushed)
605        self.assertFalse(closed)
606        sbf = lldb.SBFile(f)
607        self.assertFalse(flushed)
608        self.assertFalse(closed)
609        sbf = None
610        self.assertFalse(flushed)
611        self.assertTrue(closed)
612        self.assertTrue(f.closed)
613
614        flushed = MutableBool(False)
615        closed = MutableBool(False)
616        f = FlushTestIO(flushed, closed)
617        self.assertFalse(flushed)
618        self.assertFalse(closed)
619        sbf = lldb.SBFile.Create(f, borrow=True)
620        self.assertFalse(flushed)
621        self.assertFalse(closed)
622        sbf = None
623        self.assertTrue(flushed)
624        self.assertFalse(closed)
625        self.assertFalse(f.closed)
626
627    def test_fileno_flush(self):
628        with open(self.out_filename, "w") as f:
629            f.write("foo")
630            sbf = lldb.SBFile(f)
631            sbf.Write(b"bar")
632            sbf = None
633            self.assertTrue(f.closed)
634        with open(self.out_filename, "r") as f:
635            self.assertEqual(f.read(), "foobar")
636
637        with open(self.out_filename, "w+") as f:
638            f.write("foo")
639            sbf = lldb.SBFile.Create(f, borrow=True)
640            sbf.Write(b"bar")
641            sbf = None
642            self.assertFalse(f.closed)
643            f.seek(0)
644            self.assertEqual(f.read(), "foobar")
645
646    def test_close(self):
647        with open(self.out_filename, "w") as f:
648            status = self.dbg.SetOutputFile(f)
649            self.assertSuccess(status)
650            self.handleCmd("help help", check=False, collect_result=False)
651            # make sure the file wasn't closed early.
652            f.write("\nZAP\n")
653            lldb.SBDebugger.Destroy(self.dbg)
654            # check that output file was closed when debugger was destroyed.
655            with self.assertRaises(ValueError):
656                f.write("\nQUUX\n")
657        with open(self.out_filename, "r") as f:
658            output = f.read()
659            self.assertTrue(re.search(r"Show a list of all debugger commands", output))
660            self.assertTrue(re.search(r"ZAP", output))
661
662    def test_stdout(self):
663        f = io.StringIO()
664        status = self.dbg.SetOutputFile(f)
665        self.assertSuccess(status)
666        self.handleCmd(r"script sys.stdout.write('foobar\n')")
667        self.assertEqual(f.getvalue().strip().split(), ["foobar", "7"])
668
669    def test_stdout_file(self):
670        with open(self.out_filename, "w") as f:
671            status = self.dbg.SetOutputFile(f)
672            self.assertSuccess(status)
673            self.handleCmd(r"script sys.stdout.write('foobar\n')")
674        with open(self.out_filename, "r") as f:
675            # In python2 sys.stdout.write() returns None, which
676            # the REPL will ignore, but in python3 it will
677            # return the number of bytes written, which the REPL
678            # will print out.
679            lines = [x for x in f.read().strip().split() if x != "7"]
680            self.assertEqual(lines, ["foobar"])
681
682    def test_identity(self):
683        f = io.StringIO()
684        sbf = lldb.SBFile(f)
685        self.assertIs(f, sbf.GetFile())
686        sbf.Close()
687        self.assertTrue(f.closed)
688
689        f = io.StringIO()
690        sbf = lldb.SBFile.Create(f, borrow=True)
691        self.assertIs(f, sbf.GetFile())
692        sbf.Close()
693        self.assertFalse(f.closed)
694
695        with open(self.out_filename, "w") as f:
696            sbf = lldb.SBFile(f)
697            self.assertIs(f, sbf.GetFile())
698            sbf.Close()
699            self.assertTrue(f.closed)
700
701        with open(self.out_filename, "w") as f:
702            sbf = lldb.SBFile.Create(f, borrow=True)
703            self.assertIsNot(f, sbf.GetFile())
704            sbf.Write(b"foobar\n")
705            self.assertEqual(f.fileno(), sbf.GetFile().fileno())
706            sbf.Close()
707            self.assertFalse(f.closed)
708
709        with open(self.out_filename, "r") as f:
710            self.assertEqual("foobar", f.read().strip())
711
712        with open(self.out_filename, "wb") as f:
713            sbf = lldb.SBFile.Create(f, borrow=True, force_io_methods=True)
714            self.assertIs(f, sbf.GetFile())
715            sbf.Write(b"foobar\n")
716            self.assertEqual(f.fileno(), sbf.GetFile().fileno())
717            sbf.Close()
718            self.assertFalse(f.closed)
719
720        with open(self.out_filename, "r") as f:
721            self.assertEqual("foobar", f.read().strip())
722
723        with open(self.out_filename, "wb") as f:
724            sbf = lldb.SBFile.Create(f, force_io_methods=True)
725            self.assertIs(f, sbf.GetFile())
726            sbf.Write(b"foobar\n")
727            self.assertEqual(f.fileno(), sbf.GetFile().fileno())
728            sbf.Close()
729            self.assertTrue(f.closed)
730
731        with open(self.out_filename, "r") as f:
732            self.assertEqual("foobar", f.read().strip())
733
734    def test_back_and_forth(self):
735        with open(self.out_filename, "w") as f:
736            # at each step here we're borrowing the file, so we have to keep
737            # them all alive until the end.
738            sbf = lldb.SBFile.Create(f, borrow=True)
739
740            def i(sbf):
741                for i in range(10):
742                    f = sbf.GetFile()
743                    self.assertEqual(f.mode, "w")
744                    yield f
745                    sbf = lldb.SBFile.Create(f, borrow=True)
746                    yield sbf
747                    sbf.Write(str(i).encode("ascii") + b"\n")
748
749            files = list(i(sbf))
750        with open(self.out_filename, "r") as f:
751            self.assertEqual(list(range(10)), list(map(int, f.read().strip().split())))
752
753    def test_set_filehandle_none(self):
754        self.assertRaises(Exception, self.dbg.SetOutputFile, None)
755        self.assertRaises(Exception, self.dbg.SetOutputFile, "ham sandwich")
756        self.assertRaises(Exception, self.dbg.SetOutputFileHandle, "ham sandwich")
757        self.assertRaises(Exception, self.dbg.SetInputFile, None)
758        self.assertRaises(Exception, self.dbg.SetInputFile, "ham sandwich")
759        self.assertRaises(Exception, self.dbg.SetInputFileHandle, "ham sandwich")
760        self.assertRaises(Exception, self.dbg.SetErrorFile, None)
761        self.assertRaises(Exception, self.dbg.SetErrorFile, "ham sandwich")
762        self.assertRaises(Exception, self.dbg.SetErrorFileHandle, "ham sandwich")
763
764        with open(self.out_filename, "w") as f:
765            status = self.dbg.SetOutputFile(f)
766            self.assertSuccess(status)
767            status = self.dbg.SetErrorFile(f)
768            self.assertSuccess(status)
769            self.dbg.SetOutputFileHandle(None, False)
770            self.dbg.SetErrorFileHandle(None, False)
771            sbf = self.dbg.GetOutputFile()
772            self.assertEqual(sbf.GetFile().fileno(), 1)
773            sbf = self.dbg.GetErrorFile()
774            self.assertEqual(sbf.GetFile().fileno(), 2)
775        with open(self.out_filename, "r") as f:
776            status = self.dbg.SetInputFile(f)
777            self.assertSuccess(status)
778            self.dbg.SetInputFileHandle(None, False)
779            sbf = self.dbg.GetInputFile()
780            self.assertEqual(sbf.GetFile().fileno(), 0)
781
782    def test_sbstream(self):
783        with open(self.out_filename, "w") as f:
784            stream = lldb.SBStream()
785            stream.RedirectToFile(f)
786            stream.Print("zork")
787        with open(self.out_filename, "r") as f:
788            self.assertEqual(f.read().strip(), "zork")
789
790        with open(self.out_filename, "w") as f:
791            stream = lldb.SBStream()
792            stream.RedirectToFileHandle(f, True)
793            stream.Print("Yendor")
794        with open(self.out_filename, "r") as f:
795            self.assertEqual(f.read().strip(), "Yendor")
796
797        stream = lldb.SBStream()
798        f = open(self.out_filename, "w")
799        stream.RedirectToFile(lldb.SBFile.Create(f, borrow=False))
800        stream.Print("Frobozz")
801        stream = None
802        self.assertTrue(f.closed)
803        with open(self.out_filename, "r") as f:
804            self.assertEqual(f.read().strip(), "Frobozz")
805
806    def test_set_sbstream(self):
807        with open(self.out_filename, "w") as outf:
808            outsbf = lldb.SBFile(outf.fileno(), "w", False)
809            status = self.dbg.SetOutputFile(outsbf)
810            self.assertSuccess(status)
811            self.dbg.SetInputString("help apropos\nhelp help\n")
812
813            opts = lldb.SBCommandInterpreterRunOptions()
814            self.dbg.RunCommandInterpreter(True, False, opts, 0, False, False)
815            self.dbg.GetOutputFile().Flush()
816
817        with open(self.out_filename, "r") as f:
818            output = f.read()
819            self.assertIn("Show a list of all debugger commands", output)
820            self.assertIn("List debugger commands related to a word", output)
821