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