lldbtest.py revision 8e06de96e9d13418a0ea0d4f3311c61c2fa4fa40
1"""
2LLDB module which provides the abstract base class of lldb test case.
3
4The concrete subclass can override lldbtest.TesBase in order to inherit the
5common behavior for unitest.TestCase.setUp/tearDown implemented in this file.
6
7The subclass should override the attribute mydir in order for the python runtime
8to locate the individual test cases when running as part of a large test suite
9or when running each test case as a separate python invocation.
10
11./dotest.py provides a test driver which sets up the environment to run the
12entire test suite.  Users who want to run a test case on its own can specify the
13LLDB_TEST and PYTHONPATH environment variables, for example:
14
15$ export LLDB_TEST=$PWD
16$ export PYTHONPATH=/Volumes/data/lldb/svn/trunk/build/Debug/LLDB.framework/Resources/Python:$LLDB_TEST:$LLDB_TEST/plugins
17$ echo $LLDB_TEST
18/Volumes/data/lldb/svn/trunk/test
19$ echo $PYTHONPATH
20/Volumes/data/lldb/svn/trunk/build/Debug/LLDB.framework/Resources/Python:/Volumes/data/lldb/svn/trunk/test:/Volumes/data/lldb/svn/trunk/test/plugins
21$ python function_types/TestFunctionTypes.py
22.
23----------------------------------------------------------------------
24Ran 1 test in 0.363s
25
26OK
27$ LLDB_COMMAND_TRACE=YES python array_types/TestArrayTypes.py
28
29...
30
31runCmd: breakpoint set -f main.c -l 42
32output: Breakpoint created: 1: file ='main.c', line = 42, locations = 1
33
34runCmd: run
35output: Launching '/Volumes/data/lldb/svn/trunk/test/array_types/a.out'  (x86_64)
36
37...
38
39runCmd: frame variable strings
40output: (char *[4]) strings = {
41  (char *) strings[0] = 0x0000000100000f0c "Hello",
42  (char *) strings[1] = 0x0000000100000f12 "Hola",
43  (char *) strings[2] = 0x0000000100000f17 "Bonjour",
44  (char *) strings[3] = 0x0000000100000f1f "Guten Tag"
45}
46
47runCmd: frame variable char_16
48output: (char [16]) char_16 = {
49  (char) char_16[0] = 'H',
50  (char) char_16[1] = 'e',
51  (char) char_16[2] = 'l',
52  (char) char_16[3] = 'l',
53  (char) char_16[4] = 'o',
54  (char) char_16[5] = ' ',
55  (char) char_16[6] = 'W',
56  (char) char_16[7] = 'o',
57  (char) char_16[8] = 'r',
58  (char) char_16[9] = 'l',
59  (char) char_16[10] = 'd',
60  (char) char_16[11] = '\n',
61  (char) char_16[12] = '\0',
62  (char) char_16[13] = '\0',
63  (char) char_16[14] = '\0',
64  (char) char_16[15] = '\0'
65}
66
67runCmd: frame variable ushort_matrix
68output: (unsigned short [2][3]) ushort_matrix = {
69  (unsigned short [3]) ushort_matrix[0] = {
70    (unsigned short) ushort_matrix[0][0] = 0x0001,
71    (unsigned short) ushort_matrix[0][1] = 0x0002,
72    (unsigned short) ushort_matrix[0][2] = 0x0003
73  },
74  (unsigned short [3]) ushort_matrix[1] = {
75    (unsigned short) ushort_matrix[1][0] = 0x000b,
76    (unsigned short) ushort_matrix[1][1] = 0x0016,
77    (unsigned short) ushort_matrix[1][2] = 0x0021
78  }
79}
80
81runCmd: frame variable long_6
82output: (long [6]) long_6 = {
83  (long) long_6[0] = 1,
84  (long) long_6[1] = 2,
85  (long) long_6[2] = 3,
86  (long) long_6[3] = 4,
87  (long) long_6[4] = 5,
88  (long) long_6[5] = 6
89}
90
91.
92----------------------------------------------------------------------
93Ran 1 test in 0.349s
94
95OK
96$
97"""
98
99import os, sys, traceback
100import re
101from subprocess import *
102import time
103import types
104import unittest2
105import lldb
106
107if "LLDB_COMMAND_TRACE" in os.environ and os.environ["LLDB_COMMAND_TRACE"]=="YES":
108    traceAlways = True
109else:
110    traceAlways = False
111
112
113#
114# Some commonly used assert messages.
115#
116
117COMMAND_FAILED_AS_EXPECTED = "Command has failed as expected"
118
119CURRENT_EXECUTABLE_SET = "Current executable set successfully"
120
121PROCESS_IS_VALID = "Process is valid"
122
123PROCESS_KILLED = "Process is killed successfully"
124
125RUN_SUCCEEDED = "Process is launched successfully"
126
127RUN_COMPLETED = "Process exited successfully"
128
129BREAKPOINT_CREATED = "Breakpoint created successfully"
130
131BREAKPOINT_PENDING_CREATED = "Pending breakpoint created successfully"
132
133BREAKPOINT_HIT_ONCE = "Breakpoint resolved with hit cout = 1"
134
135STOPPED_DUE_TO_BREAKPOINT = "Process state is stopped due to breakpoint"
136
137STOPPED_DUE_TO_STEP_IN = "Process state is stopped due to step in"
138
139DATA_TYPES_DISPLAYED_CORRECTLY = "Data type(s) displayed correctly"
140
141VALID_BREAKPOINT = "Got a valid breakpoint"
142
143VALID_FILESPEC = "Got a valid filespec"
144
145VALID_PROCESS = "Got a valid process"
146
147VALID_TARGET = "Got a valid target"
148
149VARIABLES_DISPLAYED_CORRECTLY = "Variable(s) displayed correctly"
150
151
152#
153# And a generic "Command '%s' returns successfully" message generator.
154#
155def CMD_MSG(str, exe):
156    if exe:
157        return "Command '%s' returns successfully" % str
158    else:
159        return "'%s' compares successfully" % str
160
161#
162# Returns the enum from the input string.
163#
164def StopReasonEnum(string):
165    if string == "Invalid":
166        return 0
167    elif string == "None":
168        return 1
169    elif string == "Trace":
170        return 2
171    elif string == "Breakpoint":
172        return 3
173    elif string == "Watchpoint":
174        return 4
175    elif string == "Signal":
176        return 5
177    elif string == "Exception":
178        return 6
179    elif string == "PlanComplete":
180        return 7
181    else:
182        raise Exception("Unknown stopReason string")
183
184#
185# Returns the stopReason string given an enum.
186#
187def StopReasonString(enum):
188    if enum == 0:
189        return "Invalid"
190    elif enum == 1:
191        return "None"
192    elif enum == 2:
193        return "Trace"
194    elif enum == 3:
195        return "Breakpoint"
196    elif enum == 4:
197        return "Watchpoint"
198    elif enum == 5:
199        return "Signal"
200    elif enum == 6:
201        return "Exception"
202    elif enum == 7:
203        return "PlanComplete"
204    else:
205        raise Exception("Unknown stopReason enum")
206
207#
208# Returns an env variable array from the os.environ map object.
209#
210def EnvArray():
211    return map(lambda k,v: k+"="+v, os.environ.keys(), os.environ.values())
212
213# From 2.7's subprocess.check_output() convenience function.
214def system(*popenargs, **kwargs):
215    r"""Run command with arguments and return its output as a byte string.
216
217    If the exit code was non-zero it raises a CalledProcessError.  The
218    CalledProcessError object will have the return code in the returncode
219    attribute and output in the output attribute.
220
221    The arguments are the same as for the Popen constructor.  Example:
222
223    >>> check_output(["ls", "-l", "/dev/null"])
224    'crw-rw-rw- 1 root root 1, 3 Oct 18  2007 /dev/null\n'
225
226    The stdout argument is not allowed as it is used internally.
227    To capture standard error in the result, use stderr=STDOUT.
228
229    >>> check_output(["/bin/sh", "-c",
230    ...               "ls -l non_existent_file ; exit 0"],
231    ...              stderr=STDOUT)
232    'ls: non_existent_file: No such file or directory\n'
233    """
234    if 'stdout' in kwargs:
235        raise ValueError('stdout argument not allowed, it will be overridden.')
236    process = Popen(stdout=PIPE, *popenargs, **kwargs)
237    output, error = process.communicate()
238    retcode = process.poll()
239
240    if traceAlways:
241        if isinstance(popenargs, types.StringTypes):
242            args = [popenargs]
243        else:
244            args = list(popenargs)
245        print >> sys.stderr
246        print >> sys.stderr, "os command:", args
247        print >> sys.stderr, "stdout:", output
248        print >> sys.stderr, "stderr:", error
249        print >> sys.stderr, "retcode:", retcode
250        print >> sys.stderr
251
252    if retcode:
253        cmd = kwargs.get("args")
254        if cmd is None:
255            cmd = popenargs[0]
256        raise CalledProcessError(retcode, cmd)
257    return output
258
259
260class TestBase(unittest2.TestCase):
261    """This LLDB abstract base class is meant to be subclassed."""
262
263    # The concrete subclass should override this attribute.
264    mydir = None
265
266    # State pertaining to the inferior process, if any.
267    # This reflects inferior process started through the command interface with
268    # either the lldb "run" or "process launch" command.
269    # See also self.runCmd().
270    runStarted = False
271
272    # Maximum allowed attempts when launching the inferior process.
273    # Can be overridden by the LLDB_MAX_LAUNCH_COUNT environment variable.
274    maxLaunchCount = 3;
275
276    # Time to wait before the next launching attempt in second(s).
277    # Can be overridden by the LLDB_TIME_WAIT environment variable.
278    timeWait = 1.0;
279
280    # Keep track of the old current working directory.
281    oldcwd = None
282
283    @classmethod
284    def setUpClass(cls):
285        # Fail fast if 'mydir' attribute is not overridden.
286        if not cls.mydir or len(cls.mydir) == 0:
287            raise Exception("Subclasses must override the 'mydir' attribute.")
288        # Save old working directory.
289        cls.oldcwd = os.getcwd()
290
291        # Change current working directory if ${LLDB_TEST} is defined.
292        # See also dotest.py which sets up ${LLDB_TEST}.
293        if ("LLDB_TEST" in os.environ):
294            if traceAlways:
295                print "Change dir to:", os.path.join(os.environ["LLDB_TEST"], cls.mydir)
296            os.chdir(os.path.join(os.environ["LLDB_TEST"], cls.mydir))
297
298    @classmethod
299    def tearDownClass(cls):
300        """Do class-wide cleanup."""
301
302        # First, let's do the platform-specific cleanup.
303        module = __import__(sys.platform)
304        if not module.cleanup():
305            raise Exception("Don't know how to do cleanup")
306
307        # Subclass might have specific cleanup function defined.
308        if getattr(cls, "classCleanup", None):
309            if traceAlways:
310                print "Call class-specific cleanup function for class:", cls
311            try:
312                cls.classCleanup()
313            except:
314                exc_type, exc_value, exc_tb = sys.exc_info()
315                traceback.print_exception(exc_type, exc_value, exc_tb)
316
317        # Restore old working directory.
318        if traceAlways:
319            print "Restore dir to:", cls.oldcwd
320        os.chdir(cls.oldcwd)
321
322    def setUp(self):
323        #import traceback
324        #traceback.print_stack()
325
326        if "LLDB_MAX_LAUNCH_COUNT" in os.environ:
327            self.maxLaunchCount = int(os.environ["LLDB_MAX_LAUNCH_COUNT"])
328
329        if "LLDB_TIME_WAIT" in os.environ:
330            self.timeWait = float(os.environ["LLDB_TIME_WAIT"])
331
332        # Create the debugger instance if necessary.
333        try:
334            self.dbg = lldb.DBG
335        except AttributeError:
336            self.dbg = lldb.SBDebugger.Create()
337
338        if not self.dbg.IsValid():
339            raise Exception('Invalid debugger instance')
340
341        # We want our debugger to be synchronous.
342        self.dbg.SetAsync(False)
343
344        # There is no process associated with the debugger as yet.
345        # See also self.tearDown() where it checks whether self.process has a
346        # valid reference and calls self.process.Kill() to kill the process.
347        self.process = None
348
349        # Retrieve the associated command interpreter instance.
350        self.ci = self.dbg.GetCommandInterpreter()
351        if not self.ci:
352            raise Exception('Could not get the command interpreter')
353
354        # And the result object.
355        self.res = lldb.SBCommandReturnObject()
356
357    def tearDown(self):
358        #import traceback
359        #traceback.print_stack()
360
361        # Terminate the current process being debugged, if any.
362        if self.runStarted:
363            self.runCmd("process kill", PROCESS_KILLED, check=False)
364        elif self.process and self.process.IsValid():
365            rc = self.process.Kill()
366            self.assertTrue(rc.Success(), PROCESS_KILLED)
367
368        del self.dbg
369
370    def runCmd(self, cmd, msg=None, check=True, trace=False, setCookie=True):
371        """
372        Ask the command interpreter to handle the command and then check its
373        return status.
374        """
375        # Fail fast if 'cmd' is not meaningful.
376        if not cmd or len(cmd) == 0:
377            raise Exception("Bad 'cmd' parameter encountered")
378
379        trace = (True if traceAlways else trace)
380
381        running = (cmd.startswith("run") or cmd.startswith("process launch"))
382
383        for i in range(self.maxLaunchCount if running else 1):
384            self.ci.HandleCommand(cmd, self.res)
385
386            if trace:
387                print >> sys.stderr, "runCmd:", cmd
388                if self.res.Succeeded():
389                    print >> sys.stderr, "output:", self.res.GetOutput()
390                else:
391                    print >> sys.stderr, self.res.GetError()
392
393            if running:
394                # For process launch, wait some time before possible next try.
395                time.sleep(self.timeWait)
396
397            if self.res.Succeeded():
398                break
399            elif running:
400                print >> sys.stderr, "Command '" + cmd + "' failed!"
401
402        # Modify runStarted only if "run" or "process launch" was encountered.
403        if running:
404            self.runStarted = running and setCookie
405
406        if check:
407            self.assertTrue(self.res.Succeeded(),
408                            msg if msg else CMD_MSG(cmd, True))
409
410    def expect(self, str, msg=None, patterns=None, startstr=None, substrs=None, trace=False, error=False, matching=True, exe=True):
411        """
412        Similar to runCmd; with additional expect style output matching ability.
413
414        Ask the command interpreter to handle the command and then check its
415        return status.  The 'msg' parameter specifies an informational assert
416        message.  We expect the output from running the command to start with
417        'startstr', matches the substrings contained in 'substrs', and regexp
418        matches the patterns contained in 'patterns'.
419
420        If the keyword argument error is set to True, it signifies that the API
421        client is expecting the command to fail.  In this case, the error stream
422        from running the command is retrieved and compared against the golden
423        input, instead.
424
425        If the keyword argument matching is set to False, it signifies that the API
426        client is expecting the output of the command not to match the golden
427        input.
428
429        Finally, the required argument 'str' represents the lldb command to be
430        sent to the command interpreter.  In case the keyword argument 'exe' is
431        set to False, the 'str' is treated as a string to be matched/not-matched
432        against the golden input.
433        """
434        trace = (True if traceAlways else trace)
435
436        if exe:
437            # First run the command.  If we are expecting error, set check=False.
438            self.runCmd(str, trace = (True if trace else False), check = not error)
439
440            # Then compare the output against expected strings.
441            output = self.res.GetError() if error else self.res.GetOutput()
442
443            # If error is True, the API client expects the command to fail!
444            if error:
445                self.assertFalse(self.res.Succeeded(),
446                                 "Command '" + str + "' is expected to fail!")
447        else:
448            # No execution required, just compare str against the golden input.
449            output = str
450            if trace:
451                print >> sys.stderr, "look at:", output
452
453        # The heading says either "Expecting" or "Not expecting".
454        if trace:
455            heading = "Expecting" if matching else "Not expecting"
456
457        # Start from the startstr, if specified.
458        # If there's no startstr, set the initial state appropriately.
459        matched = output.startswith(startstr) if startstr else (True if matching else False)
460
461        if startstr and trace:
462            print >> sys.stderr, "%s start string: %s" % (heading, startstr)
463            print >> sys.stderr, "Matched" if matched else "Not matched"
464            print >> sys.stderr
465
466        # Look for sub strings, if specified.
467        keepgoing = matched if matching else not matched
468        if substrs and keepgoing:
469            for str in substrs:
470                matched = output.find(str) > 0
471                if trace:
472                    print >> sys.stderr, "%s sub string: %s" % (heading, str)
473                    print >> sys.stderr, "Matched" if matched else "Not matched"
474                keepgoing = matched if matching else not matched
475                if not keepgoing:
476                    break
477            if trace:
478                print >> sys.stderr
479
480        # Search for regular expression patterns, if specified.
481        keepgoing = matched if matching else not matched
482        if patterns and keepgoing:
483            for pattern in patterns:
484                # Match Objects always have a boolean value of True.
485                matched = bool(re.search(pattern, output))
486                if trace:
487                    print >> sys.stderr, "%s pattern: %s" % (heading, pattern)
488                    print >> sys.stderr, "Matched" if matched else "Not matched"
489                keepgoing = matched if matching else not matched
490                if not keepgoing:
491                    break
492            if trace:
493                print >> sys.stderr
494
495        self.assertTrue(matched if matching else not matched,
496                        msg if msg else CMD_MSG(str, exe))
497
498    def invoke(self, obj, name, trace=False):
499        """Use reflection to call a method dynamically with no argument."""
500        trace = (True if traceAlways else trace)
501
502        method = getattr(obj, name)
503        import inspect
504        self.assertTrue(inspect.ismethod(method),
505                        name + "is a method name of object: " + str(obj))
506        result = method()
507        if trace:
508            print str(method) + ":",  result
509        return result
510
511    def breakAfterLaunch(self, process, func, trace=False):
512        """
513        Perform some dancees after LaunchProcess() to break at func name.
514
515        Return True if we can successfully break at the func name in due time.
516        """
517        trace = (True if traceAlways else trace)
518
519        count = 0
520        while True:
521            # The stop reason of the thread should be breakpoint.
522            thread = process.GetThreadAtIndex(0)
523            SR = thread.GetStopReason()
524            if trace:
525                print >> sys.stderr, "StopReason =", StopReasonString(SR)
526
527            if SR == StopReasonEnum("Breakpoint"):
528                frame = thread.GetFrameAtIndex(0)
529                name = frame.GetFunction().GetName()
530                if trace:
531                    print >> sys.stderr, "function =", name
532                if (name == func):
533                    # We got what we want; now break out of the loop.
534                    return True
535
536            # The inferior is in a transient state; continue the process.
537            time.sleep(1.0)
538            if trace:
539                print >> sys.stderr, "Continuing the process:", process
540            process.Continue()
541
542            count = count + 1
543            if count == 15:
544                if trace:
545                    print >> sys.stderr, "Reached 15 iterations, giving up..."
546                # Enough iterations already, break out of the loop.
547                return False
548
549            # End of while loop.
550
551
552    def buildDefault(self, compiler=None):
553        """Platform specific way to build the default binaries."""
554        module = __import__(sys.platform)
555        if not module.buildDefault(compiler):
556            raise Exception("Don't know how to build default binary")
557
558    def buildDsym(self, compiler=None):
559        """Platform specific way to build binaries with dsym info."""
560        module = __import__(sys.platform)
561        if not module.buildDsym(compiler):
562            raise Exception("Don't know how to build binary with dsym")
563
564    def buildDwarf(self, compiler=None):
565        """Platform specific way to build binaries with dwarf maps."""
566        module = __import__(sys.platform)
567        if not module.buildDwarf(compiler):
568            raise Exception("Don't know how to build binary with dwarf")
569
570    def DebugSBValue(self, frame, val):
571        """Debug print a SBValue object, if traceAlways is True."""
572        if not traceAlways:
573            return
574
575        err = sys.stderr
576        err.write(val.GetName() + ":\n")
577        err.write('\t' + "TypeName    -> " + val.GetTypeName()          + '\n')
578        err.write('\t' + "ByteSize    -> " + str(val.GetByteSize())     + '\n')
579        err.write('\t' + "NumChildren -> " + str(val.GetNumChildren())  + '\n')
580        err.write('\t' + "Value       -> " + str(val.GetValue(frame))   + '\n')
581        err.write('\t' + "Summary     -> " + str(val.GetSummary(frame)) + '\n')
582        err.write('\t' + "IsPtrType   -> " + str(val.TypeIsPtrType())   + '\n')
583        err.write('\t' + "Location    -> " + val.GetLocation(frame)     + '\n')
584
585