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