lldbtest.py revision 41998197e9d81cba31ac4645c1ec5f0ba7ca1668
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
135BREAKPOINT_HIT_TWICE = "Breakpoint resolved with hit cout = 2"
136
137STEP_OUT_SUCCEEDED = "Thread step-out succeeded"
138
139STOPPED_DUE_TO_BREAKPOINT = "Process state is stopped due to breakpoint"
140
141STOPPED_DUE_TO_STEP_IN = "Process state is stopped due to step in"
142
143DATA_TYPES_DISPLAYED_CORRECTLY = "Data type(s) displayed correctly"
144
145VALID_BREAKPOINT = "Got a valid breakpoint"
146
147VALID_FILESPEC = "Got a valid filespec"
148
149VALID_PROCESS = "Got a valid process"
150
151VALID_TARGET = "Got a valid target"
152
153VARIABLES_DISPLAYED_CORRECTLY = "Variable(s) displayed correctly"
154
155
156#
157# And a generic "Command '%s' returns successfully" message generator.
158#
159def CMD_MSG(str, exe):
160    if exe:
161        return "Command '%s' returns successfully" % str
162    else:
163        return "'%s' compares successfully" % str
164
165#
166# Returns the enum from the input string.
167#
168def StopReasonEnum(string):
169    if string == "Invalid":
170        return 0
171    elif string == "None":
172        return 1
173    elif string == "Trace":
174        return 2
175    elif string == "Breakpoint":
176        return 3
177    elif string == "Watchpoint":
178        return 4
179    elif string == "Signal":
180        return 5
181    elif string == "Exception":
182        return 6
183    elif string == "PlanComplete":
184        return 7
185    else:
186        raise Exception("Unknown stopReason string")
187
188#
189# Returns the stopReason string given an enum.
190#
191def StopReasonString(enum):
192    if enum == 0:
193        return "Invalid"
194    elif enum == 1:
195        return "None"
196    elif enum == 2:
197        return "Trace"
198    elif enum == 3:
199        return "Breakpoint"
200    elif enum == 4:
201        return "Watchpoint"
202    elif enum == 5:
203        return "Signal"
204    elif enum == 6:
205        return "Exception"
206    elif enum == 7:
207        return "PlanComplete"
208    else:
209        raise Exception("Unknown stopReason enum")
210
211#
212# Returns an env variable array from the os.environ map object.
213#
214def EnvArray():
215    return map(lambda k,v: k+"="+v, os.environ.keys(), os.environ.values())
216
217# From 2.7's subprocess.check_output() convenience function.
218def system(*popenargs, **kwargs):
219    r"""Run command with arguments and return its output as a byte string.
220
221    If the exit code was non-zero it raises a CalledProcessError.  The
222    CalledProcessError object will have the return code in the returncode
223    attribute and output in the output attribute.
224
225    The arguments are the same as for the Popen constructor.  Example:
226
227    >>> check_output(["ls", "-l", "/dev/null"])
228    'crw-rw-rw- 1 root root 1, 3 Oct 18  2007 /dev/null\n'
229
230    The stdout argument is not allowed as it is used internally.
231    To capture standard error in the result, use stderr=STDOUT.
232
233    >>> check_output(["/bin/sh", "-c",
234    ...               "ls -l non_existent_file ; exit 0"],
235    ...              stderr=STDOUT)
236    'ls: non_existent_file: No such file or directory\n'
237    """
238    if 'stdout' in kwargs:
239        raise ValueError('stdout argument not allowed, it will be overridden.')
240    process = Popen(stdout=PIPE, *popenargs, **kwargs)
241    output, error = process.communicate()
242    retcode = process.poll()
243
244    if traceAlways:
245        if isinstance(popenargs, types.StringTypes):
246            args = [popenargs]
247        else:
248            args = list(popenargs)
249        print >> sys.stderr
250        print >> sys.stderr, "os command:", args
251        print >> sys.stderr, "stdout:", output
252        print >> sys.stderr, "stderr:", error
253        print >> sys.stderr, "retcode:", retcode
254        print >> sys.stderr
255
256    if retcode:
257        cmd = kwargs.get("args")
258        if cmd is None:
259            cmd = popenargs[0]
260        raise CalledProcessError(retcode, cmd)
261    return output
262
263
264class TestBase(unittest2.TestCase):
265    """This LLDB abstract base class is meant to be subclassed."""
266
267    @classmethod
268    def skipLongRunningTest(cls):
269        """
270        By default, we skip long running test case.
271        This can be overridden by passing '-l' to the test driver (dotest.py).
272        """
273        if "LLDB_SKIP_LONG_RUNNING_TEST" in os.environ and "NO" == os.environ["LLDB_SKIP_LONG_RUNNING_TEST"]:
274            return False
275        else:
276            return True
277
278    # The concrete subclass should override this attribute.
279    mydir = None
280
281    # State pertaining to the inferior process, if any.
282    # This reflects inferior process started through the command interface with
283    # either the lldb "run" or "process launch" command.
284    # See also self.runCmd().
285    runStarted = False
286
287    # Maximum allowed attempts when launching the inferior process.
288    # Can be overridden by the LLDB_MAX_LAUNCH_COUNT environment variable.
289    maxLaunchCount = 3;
290
291    # Time to wait before the next launching attempt in second(s).
292    # Can be overridden by the LLDB_TIME_WAIT environment variable.
293    timeWait = 1.0;
294
295    # Keep track of the old current working directory.
296    oldcwd = None
297
298    @classmethod
299    def setUpClass(cls):
300        """
301        Python unittest framework class setup fixture.
302        Do current directory manipulation.
303        """
304
305        # Fail fast if 'mydir' attribute is not overridden.
306        if not cls.mydir or len(cls.mydir) == 0:
307            raise Exception("Subclasses must override the 'mydir' attribute.")
308        # Save old working directory.
309        cls.oldcwd = os.getcwd()
310
311        # Change current working directory if ${LLDB_TEST} is defined.
312        # See also dotest.py which sets up ${LLDB_TEST}.
313        if ("LLDB_TEST" in os.environ):
314            if traceAlways:
315                print >> sys.stderr, "Change dir to:", os.path.join(os.environ["LLDB_TEST"], cls.mydir)
316            os.chdir(os.path.join(os.environ["LLDB_TEST"], cls.mydir))
317
318    @classmethod
319    def tearDownClass(cls):
320        """
321        Python unittest framework class teardown fixture.
322        Do class-wide cleanup.
323        """
324
325        # First, let's do the platform-specific cleanup.
326        module = __import__(sys.platform)
327        if not module.cleanup():
328            raise Exception("Don't know how to do cleanup")
329
330        # Subclass might have specific cleanup function defined.
331        if getattr(cls, "classCleanup", None):
332            if traceAlways:
333                print >> sys.stderr, "Call class-specific cleanup function for class:", cls
334            try:
335                cls.classCleanup()
336            except:
337                exc_type, exc_value, exc_tb = sys.exc_info()
338                traceback.print_exception(exc_type, exc_value, exc_tb)
339
340        # Restore old working directory.
341        if traceAlways:
342            print >> sys.stderr, "Restore dir to:", cls.oldcwd
343        os.chdir(cls.oldcwd)
344
345    def setUp(self):
346        #import traceback
347        #traceback.print_stack()
348
349        if "LLDB_MAX_LAUNCH_COUNT" in os.environ:
350            self.maxLaunchCount = int(os.environ["LLDB_MAX_LAUNCH_COUNT"])
351
352        if "LLDB_TIME_WAIT" in os.environ:
353            self.timeWait = float(os.environ["LLDB_TIME_WAIT"])
354
355        # Create the debugger instance if necessary.
356        try:
357            self.dbg = lldb.DBG
358        except AttributeError:
359            self.dbg = lldb.SBDebugger.Create()
360
361        if not self.dbg.IsValid():
362            raise Exception('Invalid debugger instance')
363
364        # We want our debugger to be synchronous.
365        self.dbg.SetAsync(False)
366
367        # There is no process associated with the debugger as yet.
368        # See also self.tearDown() where it checks whether self.process has a
369        # valid reference and calls self.process.Kill() to kill the process.
370        self.process = None
371
372        # Retrieve the associated command interpreter instance.
373        self.ci = self.dbg.GetCommandInterpreter()
374        if not self.ci:
375            raise Exception('Could not get the command interpreter')
376
377        # And the result object.
378        self.res = lldb.SBCommandReturnObject()
379
380        # These are for customized teardown cleanup.
381        self.dict = None
382        self.doTearDownCleanup = False
383
384    def setTearDownCleanup(self, dictionary=None):
385        self.dict = dictionary
386        self.doTearDownCleanup = True
387
388    def tearDown(self):
389        #import traceback
390        #traceback.print_stack()
391
392        # Terminate the current process being debugged, if any.
393        if self.runStarted:
394            self.runCmd("process kill", PROCESS_KILLED, check=False)
395        elif self.process and self.process.IsValid():
396            rc = self.process.Kill()
397            self.assertTrue(rc.Success(), PROCESS_KILLED)
398
399        del self.dbg
400
401        # Perform registered teardown cleanup.
402        if self.doTearDownCleanup:
403            module = __import__(sys.platform)
404            if not module.cleanup(dictionary=self.dict):
405                raise Exception("Don't know how to do cleanup")
406
407    def runCmd(self, cmd, msg=None, check=True, trace=False, setCookie=True):
408        """
409        Ask the command interpreter to handle the command and then check its
410        return status.
411        """
412        # Fail fast if 'cmd' is not meaningful.
413        if not cmd or len(cmd) == 0:
414            raise Exception("Bad 'cmd' parameter encountered")
415
416        trace = (True if traceAlways else trace)
417
418        running = (cmd.startswith("run") or cmd.startswith("process launch"))
419
420        for i in range(self.maxLaunchCount if running else 1):
421            self.ci.HandleCommand(cmd, self.res)
422
423            if trace:
424                print >> sys.stderr, "runCmd:", cmd
425                if self.res.Succeeded():
426                    print >> sys.stderr, "output:", self.res.GetOutput()
427                else:
428                    print >> sys.stderr, self.res.GetError()
429
430            if running:
431                # For process launch, wait some time before possible next try.
432                time.sleep(self.timeWait)
433
434            if self.res.Succeeded():
435                break
436            elif running:
437                print >> sys.stderr, "Command '" + cmd + "' failed!"
438
439        # Modify runStarted only if "run" or "process launch" was encountered.
440        if running:
441            self.runStarted = running and setCookie
442
443        if check:
444            self.assertTrue(self.res.Succeeded(),
445                            msg if msg else CMD_MSG(cmd, True))
446
447    def expect(self, str, msg=None, patterns=None, startstr=None, substrs=None, trace=False, error=False, matching=True, exe=True):
448        """
449        Similar to runCmd; with additional expect style output matching ability.
450
451        Ask the command interpreter to handle the command and then check its
452        return status.  The 'msg' parameter specifies an informational assert
453        message.  We expect the output from running the command to start with
454        'startstr', matches the substrings contained in 'substrs', and regexp
455        matches the patterns contained in 'patterns'.
456
457        If the keyword argument error is set to True, it signifies that the API
458        client is expecting the command to fail.  In this case, the error stream
459        from running the command is retrieved and compared against the golden
460        input, instead.
461
462        If the keyword argument matching is set to False, it signifies that the API
463        client is expecting the output of the command not to match the golden
464        input.
465
466        Finally, the required argument 'str' represents the lldb command to be
467        sent to the command interpreter.  In case the keyword argument 'exe' is
468        set to False, the 'str' is treated as a string to be matched/not-matched
469        against the golden input.
470        """
471        trace = (True if traceAlways else trace)
472
473        if exe:
474            # First run the command.  If we are expecting error, set check=False.
475            self.runCmd(str, trace = (True if trace else False), check = not error)
476
477            # Then compare the output against expected strings.
478            output = self.res.GetError() if error else self.res.GetOutput()
479
480            # If error is True, the API client expects the command to fail!
481            if error:
482                self.assertFalse(self.res.Succeeded(),
483                                 "Command '" + str + "' is expected to fail!")
484        else:
485            # No execution required, just compare str against the golden input.
486            output = str
487            if trace:
488                print >> sys.stderr, "looking at:", output
489
490        # The heading says either "Expecting" or "Not expecting".
491        if trace:
492            heading = "Expecting" if matching else "Not expecting"
493
494        # Start from the startstr, if specified.
495        # If there's no startstr, set the initial state appropriately.
496        matched = output.startswith(startstr) if startstr else (True if matching else False)
497
498        if startstr and trace:
499            print >> sys.stderr, "%s start string: %s" % (heading, startstr)
500            print >> sys.stderr, "Matched" if matched else "Not matched"
501            print >> sys.stderr
502
503        # Look for sub strings, if specified.
504        keepgoing = matched if matching else not matched
505        if substrs and keepgoing:
506            for str in substrs:
507                matched = output.find(str) != -1
508                if trace:
509                    print >> sys.stderr, "%s sub string: %s" % (heading, str)
510                    print >> sys.stderr, "Matched" if matched else "Not matched"
511                keepgoing = matched if matching else not matched
512                if not keepgoing:
513                    break
514            if trace:
515                print >> sys.stderr
516
517        # Search for regular expression patterns, if specified.
518        keepgoing = matched if matching else not matched
519        if patterns and keepgoing:
520            for pattern in patterns:
521                # Match Objects always have a boolean value of True.
522                matched = bool(re.search(pattern, output))
523                if trace:
524                    print >> sys.stderr, "%s pattern: %s" % (heading, pattern)
525                    print >> sys.stderr, "Matched" if matched else "Not matched"
526                keepgoing = matched if matching else not matched
527                if not keepgoing:
528                    break
529            if trace:
530                print >> sys.stderr
531
532        self.assertTrue(matched if matching else not matched,
533                        msg if msg else CMD_MSG(str, exe))
534
535    def invoke(self, obj, name, trace=False):
536        """Use reflection to call a method dynamically with no argument."""
537        trace = (True if traceAlways else trace)
538
539        method = getattr(obj, name)
540        import inspect
541        self.assertTrue(inspect.ismethod(method),
542                        name + "is a method name of object: " + str(obj))
543        result = method()
544        if trace:
545            print >> sys.stderr, str(method) + ":",  result
546        return result
547
548    def breakAfterLaunch(self, process, func, trace=False):
549        """
550        Perform some dancees after LaunchProcess() to break at func name.
551
552        Return True if we can successfully break at the func name in due time.
553        """
554        trace = (True if traceAlways else trace)
555
556        count = 0
557        while True:
558            # The stop reason of the thread should be breakpoint.
559            thread = process.GetThreadAtIndex(0)
560            SR = thread.GetStopReason()
561            if trace:
562                print >> sys.stderr, "StopReason =", StopReasonString(SR)
563
564            if SR == StopReasonEnum("Breakpoint"):
565                frame = thread.GetFrameAtIndex(0)
566                name = frame.GetFunction().GetName()
567                if trace:
568                    print >> sys.stderr, "function =", name
569                if (name == func):
570                    # We got what we want; now break out of the loop.
571                    return True
572
573            # The inferior is in a transient state; continue the process.
574            time.sleep(1.0)
575            if trace:
576                print >> sys.stderr, "Continuing the process:", process
577            process.Continue()
578
579            count = count + 1
580            if count == 15:
581                if trace:
582                    print >> sys.stderr, "Reached 15 iterations, giving up..."
583                # Enough iterations already, break out of the loop.
584                return False
585
586            # End of while loop.
587
588
589    def buildDefault(self, architecture=None, compiler=None, dictionary=None):
590        """Platform specific way to build the default binaries."""
591        module = __import__(sys.platform)
592        if not module.buildDefault(architecture, compiler, dictionary):
593            raise Exception("Don't know how to build default binary")
594
595    def buildDsym(self, architecture=None, compiler=None, dictionary=None):
596        """Platform specific way to build binaries with dsym info."""
597        module = __import__(sys.platform)
598        if not module.buildDsym(architecture, compiler, dictionary):
599            raise Exception("Don't know how to build binary with dsym")
600
601    def buildDwarf(self, architecture=None, compiler=None, dictionary=None):
602        """Platform specific way to build binaries with dwarf maps."""
603        module = __import__(sys.platform)
604        if not module.buildDwarf(architecture, compiler, dictionary):
605            raise Exception("Don't know how to build binary with dwarf")
606
607    def DebugSBValue(self, frame, val):
608        """Debug print a SBValue object, if traceAlways is True."""
609        if not traceAlways:
610            return
611
612        err = sys.stderr
613        err.write(val.GetName() + ":\n")
614        err.write('\t' + "TypeName    -> " + val.GetTypeName()          + '\n')
615        err.write('\t' + "ByteSize    -> " + str(val.GetByteSize())     + '\n')
616        err.write('\t' + "NumChildren -> " + str(val.GetNumChildren())  + '\n')
617        err.write('\t' + "Value       -> " + str(val.GetValue(frame))   + '\n')
618        err.write('\t' + "Summary     -> " + str(val.GetSummary(frame)) + '\n')
619        err.write('\t' + "IsPtrType   -> " + str(val.TypeIsPtrType())   + '\n')
620        err.write('\t' + "Location    -> " + val.GetLocation(frame)     + '\n')
621
622