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