Home | History | Annotate | Download | only in testsuite
      1 Let's pick test/settings/TestSettings.py as our example.  First, notice the file
      2 name "TestSettings.py", the Test*.py pattern is the default mechanism that the
      3 test driver uses for discovery of tests.  As to TestSettings.py, it defines a
      4 class:
      5 
      6 class SettingsCommandTestCase(TestBase):
      7 
      8 derived from TestBase, which is defined in test/lldbtest.py and is itself
      9 derived from Python's unittest framework's TestCase class.  See also
     10 http://docs.python.org/library/unittest.html for more details.
     11 
     12 To just run the TestSettings.py test, chdir to the lldb test directory, and then
     13 type the following command:
     14 
     15 /Volumes/data/lldb/svn/trunk/test $ ./dotest.py settings
     16 ----------------------------------------------------------------------
     17 Collected 6 tests
     18 
     19 ----------------------------------------------------------------------
     20 Ran 6 tests in 8.699s
     21 
     22 OK (expected failures=1)
     23 /Volumes/data/lldb/svn/trunk/test $ 
     24 
     25 Pass '-v' option to the test driver to also output verbose descriptions of the
     26 individual test cases and their test status:
     27 
     28 /Volumes/data/lldb/svn/trunk/test $ ./dotest.py -v settings
     29 ----------------------------------------------------------------------
     30 Collected 6 tests
     31 
     32 test_set_auto_confirm (TestSettings.SettingsCommandTestCase)
     33 Test that after 'set auto-confirm true', manual confirmation should not kick in. ... ok
     34 test_set_output_path (TestSettings.SettingsCommandTestCase)
     35 Test that setting target.process.output-path for the launched process works. ... expected failure
     36 test_set_prompt (TestSettings.SettingsCommandTestCase)
     37 Test that 'set prompt' actually changes the prompt. ... ok
     38 test_set_term_width (TestSettings.SettingsCommandTestCase)
     39 Test that 'set term-width' actually changes the term-width. ... ok
     40 test_with_dsym (TestSettings.SettingsCommandTestCase)
     41 Test that run-args and env-vars are passed to the launched process. ... ok
     42 test_with_dwarf (TestSettings.SettingsCommandTestCase)
     43 Test that run-args and env-vars are passed to the launched process. ... ok
     44 
     45 ----------------------------------------------------------------------
     46 Ran 6 tests in 5.735s
     47 
     48 OK (expected failures=1)
     49 /Volumes/data/lldb/svn/trunk/test $ 
     50 
     51 Underneath, the '-v' option passes keyword argument verbosity=2 to the
     52 Python's unittest.TextTestRunner (see also
     53 http://docs.python.org/library/unittest.html#unittest.TextTestRunner).  For very
     54 detailed descriptions about what's going on during the test, pass '-t' to the
     55 test driver, which asks the test driver to trace the commands executed and to
     56 display their output.  For brevity, the '-t' output is not included here.
     57 
     58 Notice the 'expected failures=1' message at the end of the run.  This is because
     59 of a bug currently in lldb such that setting target.process.output-path to
     60 'stdout.txt' does not have any effect on the redirection of the standard output
     61 of the subsequent launched process.  We are using unittest2 (a backport of new
     62 unittest features for Python 2.4-2.6) to decorate (mark) the particular test
     63 method as such:
     64 
     65     @unittest2.expectedFailure
     66     # rdar://problem/8435794
     67     # settings set target.process.output-path does not seem to work
     68     def test_set_output_path(self):
     69 
     70 See http://pypi.python.org/pypi/unittest2 for more details.
     71 
     72 Now let's look inside the test method:
     73 
     74     def test_set_output_path(self):
     75         """Test that setting target.process.output-path for the launched process works."""
     76         self.buildDefault()
     77 
     78         exe = os.path.join(os.getcwd(), "a.out")
     79         self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
     80 
     81         # Set the output-path and verify it is set.
     82         self.runCmd("settings set target.process.output-path 'stdout.txt'")
     83         self.expect("settings show target.process.output-path",
     84             startstr = "target.process.output-path (string) = 'stdout.txt'")
     85 
     86         self.runCmd("run", RUN_SUCCEEDED)
     87 
     88         # The 'stdout.txt' file should now exist.
     89         self.assertTrue(os.path.isfile("stdout.txt"),
     90                         "'stdout.txt' exists due to target.process.output-path.")
     91 
     92         # Read the output file produced by running the program.
     93         with open('stdout.txt', 'r') as f:
     94             output = f.read()
     95 
     96         self.expect(output, exe=False,
     97             startstr = "This message should go to standard out.")
     98 
     99 The self.buildDefault() statement is used to build a default binary for this
    100 test instance.  For this particular test case, since we don't really care what
    101 debugging format is used, we instruct the build subsystem to build the default
    102 binary for us.  The base class TestBase has defined three instance methods:
    103 
    104     def buildDefault(self, architecture=None, compiler=None, dictionary=None):
    105         """Platform specific way to build the default binaries."""
    106         module = __import__(sys.platform)
    107         if not module.buildDefault(self, architecture, compiler, dictionary):
    108             raise Exception("Don't know how to build default binary")
    109 
    110     def buildDsym(self, architecture=None, compiler=None, dictionary=None):
    111         """Platform specific way to build binaries with dsym info."""
    112         module = __import__(sys.platform)
    113         if not module.buildDsym(self, architecture, compiler, dictionary):
    114             raise Exception("Don't know how to build binary with dsym")
    115 
    116     def buildDwarf(self, architecture=None, compiler=None, dictionary=None):
    117         """Platform specific way to build binaries with dwarf maps."""
    118         module = __import__(sys.platform)
    119         if not module.buildDwarf(self, architecture, compiler, dictionary):
    120             raise Exception("Don't know how to build binary with dwarf")
    121 
    122 And the test/plugins/darwin.py provides the implementation for all three build
    123 methods using the makefile mechanism.  We envision that linux plugin can use a
    124 similar approach to accomplish the task of building the binaries.
    125 
    126 Mac OS X provides an additional way to manipulate archived DWARF debug symbol
    127 files and produces dSYM files.  The buildDsym() instance method is used by the
    128 test method to build the binary with dsym info.  For an example of this,
    129 see test/array_types/TestArrayTypes.py:
    130 
    131     @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin")
    132     def test_with_dsym_and_run_command(self):
    133         """Test 'frame variable var_name' on some variables with array types."""
    134         self.buildDsym()
    135         self.array_types()
    136 
    137 This method is decorated with a skipUnless decorator so that it will only gets
    138 included into the test suite if the platform it is running on is 'darwin', aka
    139 Mac OS X.
    140 
    141 Type 'man dsymutil' for more details. 
    142 
    143 After the binary is built, it is time to specify the file to be used as the main
    144 executable by lldb:
    145 
    146         exe = os.path.join(os.getcwd(), "a.out")
    147         self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
    148 
    149 This is where the attribute assignment:
    150 
    151 class SettingsCommandTestCase(TestBase):
    152 
    153     mydir = "settings"
    154 
    155 which happens right after the SettingsCommandTestCase class declaration comes
    156 into place. It specifies the relative directory to the top level 'test' so that
    157 the test harness can change its working directory in order to find the
    158 executable as well as the source code files. The runCmd() method is defined in
    159 the TestBase base class (within test/lldbtest.py) and its purpose is to pass the
    160 specified command to the lldb command interpreter. It's like you're typing the
    161 command within an interactive lldb session.
    162 
    163 The CURRENT_EXECUTABLE_SET is an assert message defined in the lldbtest module
    164 so that it can be reused from other test modules.
    165 
    166 By default, the runCmd() is going to check the return status of the command
    167 execution and fails the test if it is not a success.  The assert message, in our
    168 case CURRENT_EXECUTABLE_SET, is used in the exception printout if this happens.
    169 
    170 There are cases when we don't care about the return status from the command
    171 execution.  This can be accomplished by passing the keyword argument pair
    172 'check=False' to the method.
    173 
    174 After the current executable is set, we'll then execute two more commands:
    175 
    176         # Set the output-path and verify it is set.
    177         self.runCmd("settings set target.process.output-path 'stdout.txt'")
    178         self.expect("settings show target.process.output-path",
    179                     SETTING_MSG("target.process.output-path"),
    180             startstr = "target.process.output-path (string) = 'stdout.txt'")
    181 
    182 The first uses the 'settings set' command to set the static setting
    183 target.process.output-path to be 'stdout.txt', instead of the default
    184 '/dev/stdout'.  We then immediately issue a 'settings show' command to check
    185 that, indeed, the setting did take place.  Notice that we use a new method
    186 expect() to accomplish the task, which in effect issues a runCmd() behind the
    187 door and grabs the output from the command execution and expects to match the
    188 start string of the output against what we pass in as the value of the keyword
    189 argument pair:
    190 
    191             startstr = "target.process.output-path (string) = 'stdout.txt'"
    192 
    193 Take a look at TestBase.expect() within lldbtest.py for more details.  Among
    194 other things, it can also match against a list of regexp patterns as well as a
    195 list of sub strings.  And it can also perform negative matching, i.e., instead
    196 of expecting something from the output of command execution, it can perform the
    197 action of 'not expecting' something.
    198 
    199 This will launch/run the program:
    200 
    201         self.runCmd("run", RUN_SUCCEEDED)
    202 
    203 And this asserts that the file 'stdout.txt' should be present after running the
    204 program.
    205 
    206         # The 'stdout.txt' file should now exist.
    207         self.assertTrue(os.path.isfile("stdout.txt"),
    208                         "'stdout.txt' exists due to target.process.output-path.")
    209 
    210 Also take a look at main.cpp which emits some message to the stdout.  Now, if we
    211 pass this assertion, it's time to examine the contents of the file to make sure
    212 it contains the same message as programmed in main.cpp:
    213 
    214         # Read the output file produced by running the program.
    215         with open('stdout.txt', 'r') as f:
    216             output = f.read()
    217 
    218         self.expect(output, exe=False,
    219             startstr = "This message should go to standard out.")
    220 
    221 We open the file and read its contents into output, then issue an expect()
    222 method.  The 'exe=False' keyword argument pair tells expect() that don't try to
    223 execute the first arg as a command at all.  Instead, treat it as a string to
    224 match against whatever is thrown in as keyword argument pairs!
    225 
    226 There are also other test methods present in the TestSettings.py mode:
    227 test_set_prompt(), test_set_term_width(), test_set_auto_confirm(),
    228 test_with_dsym(), and test_with_dwarf().  We are using the default test loader
    229 from unittest framework, which uses the 'test' method name prefix to identify
    230 test methods automatically.
    231 
    232 This finishes the walkthrough of the test method test_set_output_path(self).
    233 Before we say goodbye, notice the little method definition at the top of the
    234 file:
    235 
    236     @classmethod
    237     def classCleanup(cls):
    238         system(["/bin/sh", "-c", "rm -f output.txt"])
    239         system(["/bin/sh", "-c", "rm -f stdout.txt"])
    240 
    241 This is a classmethod (as shown by the @classmethod decorator) which allows the
    242 individual test class to perform cleanup actions after the test harness finishes
    243 with the particular test class.  This is part of the so-called test fixture in
    244 the unittest framework.  From http://docs.python.org/library/unittest.html:
    245 
    246 A test fixture represents the preparation needed to perform one or more tests,
    247 and any associate cleanup actions. This may involve, for example, creating
    248 temporary or proxy databases, directories, or starting a server process.
    249 
    250 The TestBase class uses such fixture with setUp(self), tearDown(self),
    251 setUpClass(cls), and tearDownClass(cls).  And within teraDownClass(cls), it
    252 checks whether the current class has an attribute named 'classCleanup', and
    253 executes as a method if present.  In this particular case, the classCleanup()
    254 calls a utility function system() defined in lldbtest.py in order to remove the
    255 files created by running the program as the tests are executed.
    256 
    257 This system() function uses the Python subprocess module to spawn the process
    258 and to retrieve its results.  If the test instance passes the keyword argument
    259 pair 'sender=self', the detailed command execution through the operating system
    260 also gets recorded in a session object.  If the test instance fails or errors,
    261 the session info automatically gets dumped to a file grouped under a directory
    262 named after the timestamp of the particular test suite run.
    263 
    264 For simple cases, look for the timestamp directory in the same directory of the
    265 test driver program dotest.py.  For example, if we comment out the
    266 @expectedFailure decorator for TestSettings.py, and then run the test module:
    267 
    268 /Volumes/data/lldb/svn/trunk/test $ ./dotest.py -v settings
    269 ----------------------------------------------------------------------
    270 Collected 6 tests
    271 
    272 test_set_auto_confirm (TestSettings.SettingsCommandTestCase)
    273 Test that after 'set auto-confirm true', manual confirmation should not kick in. ... ok
    274 test_set_output_path (TestSettings.SettingsCommandTestCase)
    275 Test that setting target.process.output-path for the launched process works. ... FAIL
    276 test_set_prompt (TestSettings.SettingsCommandTestCase)
    277 Test that 'set prompt' actually changes the prompt. ... ok
    278 test_set_term_width (TestSettings.SettingsCommandTestCase)
    279 Test that 'set term-width' actually changes the term-width. ... ok
    280 test_with_dsym (TestSettings.SettingsCommandTestCase)
    281 Test that run-args and env-vars are passed to the launched process. ... ok
    282 test_with_dwarf (TestSettings.SettingsCommandTestCase)
    283 Test that run-args and env-vars are passed to the launched process. ... ok
    284 
    285 ======================================================================
    286 FAIL: test_set_output_path (TestSettings.SettingsCommandTestCase)
    287 Test that setting target.process.output-path for the launched process works.
    288 ----------------------------------------------------------------------
    289 Traceback (most recent call last):
    290   File "/Volumes/data/lldb/svn/trunk/test/settings/TestSettings.py", line 125, in test_set_output_path
    291     "'stdout.txt' exists due to target.process.output-path.")
    292 AssertionError: False is not True : 'stdout.txt' exists due to target.process.output-path.
    293 
    294 ----------------------------------------------------------------------
    295 Ran 6 tests in 8.219s
    296 
    297 FAILED (failures=1)
    298 /Volumes/data/lldb/svn/trunk/test $ ls 2010-10-19-14:10:49.059609
    299 
    300 NOTE: This directory name has been changed to not contain the ':' character
    301       which is not allowed in windows platforms.  We'll change the ':' to '_'
    302       and get rid of the microsecond resolution by modifying the test driver.
    303 
    304 TestSettings.SettingsCommandTestCase.test_set_output_path.log
    305 /Volumes/data/lldb/svn/trunk/test $ 
    306 
    307 We get one failure and a timestamp directory 2010-10-19-14:10:49.059609.
    308 For education purposes, the directory and its contents are reproduced here in
    309 the same directory as the current file.
    310