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