1Let's pick test/settings/TestSettings.py as our example. First, notice the file 2name "TestSettings.py", the Test*.py pattern is the default mechanism that the 3test driver uses for discovery of tests. As to TestSettings.py, it defines a 4class: 5 6class SettingsCommandTestCase(TestBase): 7 8derived from TestBase, which is defined in test/lldbtest.py and is itself 9derived from Python's unittest framework's TestCase class. See also 10http://docs.python.org/library/unittest.html for more details. 11 12To just run the TestSettings.py test, chdir to the lldb test directory, and then 13type the following command: 14 15/Volumes/data/lldb/svn/trunk/test $ ./dotest.py settings 16---------------------------------------------------------------------- 17Collected 6 tests 18 19---------------------------------------------------------------------- 20Ran 6 tests in 8.699s 21 22OK (expected failures=1) 23/Volumes/data/lldb/svn/trunk/test $ 24 25Pass '-v' option to the test driver to also output verbose descriptions of the 26individual test cases and their test status: 27 28/Volumes/data/lldb/svn/trunk/test $ ./dotest.py -v settings 29---------------------------------------------------------------------- 30Collected 6 tests 31 32test_set_auto_confirm (TestSettings.SettingsCommandTestCase) 33Test that after 'set auto-confirm true', manual confirmation should not kick in. ... ok 34test_set_output_path (TestSettings.SettingsCommandTestCase) 35Test that setting target.process.output-path for the launched process works. ... expected failure 36test_set_prompt (TestSettings.SettingsCommandTestCase) 37Test that 'set prompt' actually changes the prompt. ... ok 38test_set_term_width (TestSettings.SettingsCommandTestCase) 39Test that 'set term-width' actually changes the term-width. ... ok 40test_with_dsym (TestSettings.SettingsCommandTestCase) 41Test that run-args and env-vars are passed to the launched process. ... ok 42test_with_dwarf (TestSettings.SettingsCommandTestCase) 43Test that run-args and env-vars are passed to the launched process. ... ok 44 45---------------------------------------------------------------------- 46Ran 6 tests in 5.735s 47 48OK (expected failures=1) 49/Volumes/data/lldb/svn/trunk/test $ 50 51Underneath, the '-v' option passes keyword argument verbosity=2 to the 52Python's unittest.TextTestRunner (see also 53http://docs.python.org/library/unittest.html#unittest.TextTestRunner). For very 54detailed descriptions about what's going on during the test, pass '-t' to the 55test driver, which asks the test driver to trace the commands executed and to 56display their output. For brevity, the '-t' output is not included here. 57 58Notice the 'expected failures=1' message at the end of the run. This is because 59of 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 61of the subsequent launched process. We are using unittest2 (a backport of new 62unittest features for Python 2.4-2.6) to decorate (mark) the particular test 63method 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 70See http://pypi.python.org/pypi/unittest2 for more details. 71 72Now 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 99The self.buildDefault() statement is used to build a default binary for this 100test instance. For this particular test case, since we don't really care what 101debugging format is used, we instruct the build subsystem to build the default 102binary 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 122And the test/plugins/darwin.py provides the implementation for all three build 123methods using the makefile mechanism. We envision that linux plugin can use a 124similar approach to accomplish the task of building the binaries. 125 126Mac OS X provides an additional way to manipulate archived DWARF debug symbol 127files and produces dSYM files. The buildDsym() instance method is used by the 128test method to build the binary with dsym info. For an example of this, 129see 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 137This method is decorated with a skipUnless decorator so that it will only gets 138included into the test suite if the platform it is running on is 'darwin', aka 139Mac OS X. 140 141Type 'man dsymutil' for more details. 142 143After the binary is built, it is time to specify the file to be used as the main 144executable by lldb: 145 146 exe = os.path.join(os.getcwd(), "a.out") 147 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) 148 149This is where the attribute assignment: 150 151class SettingsCommandTestCase(TestBase): 152 153 mydir = "settings" 154 155which happens right after the SettingsCommandTestCase class declaration comes 156into place. It specifies the relative directory to the top level 'test' so that 157the test harness can change its working directory in order to find the 158executable as well as the source code files. The runCmd() method is defined in 159the TestBase base class (within test/lldbtest.py) and its purpose is to pass the 160specified command to the lldb command interpreter. It's like you're typing the 161command within an interactive lldb session. 162 163The CURRENT_EXECUTABLE_SET is an assert message defined in the lldbtest module 164so that it can be reused from other test modules. 165 166By default, the runCmd() is going to check the return status of the command 167execution and fails the test if it is not a success. The assert message, in our 168case CURRENT_EXECUTABLE_SET, is used in the exception printout if this happens. 169 170There are cases when we don't care about the return status from the command 171execution. This can be accomplished by passing the keyword argument pair 172'check=False' to the method. 173 174After 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 182The first uses the 'settings set' command to set the static setting 183target.process.output-path to be 'stdout.txt', instead of the default 184'/dev/stdout'. We then immediately issue a 'settings show' command to check 185that, indeed, the setting did take place. Notice that we use a new method 186expect() to accomplish the task, which in effect issues a runCmd() behind the 187door and grabs the output from the command execution and expects to match the 188start string of the output against what we pass in as the value of the keyword 189argument pair: 190 191 startstr = "target.process.output-path (string) = 'stdout.txt'" 192 193Take a look at TestBase.expect() within lldbtest.py for more details. Among 194other things, it can also match against a list of regexp patterns as well as a 195list of sub strings. And it can also perform negative matching, i.e., instead 196of expecting something from the output of command execution, it can perform the 197action of 'not expecting' something. 198 199This will launch/run the program: 200 201 self.runCmd("run", RUN_SUCCEEDED) 202 203And this asserts that the file 'stdout.txt' should be present after running the 204program. 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 210Also take a look at main.cpp which emits some message to the stdout. Now, if we 211pass this assertion, it's time to examine the contents of the file to make sure 212it 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 221We open the file and read its contents into output, then issue an expect() 222method. The 'exe=False' keyword argument pair tells expect() that don't try to 223execute the first arg as a command at all. Instead, treat it as a string to 224match against whatever is thrown in as keyword argument pairs! 225 226There are also other test methods present in the TestSettings.py mode: 227test_set_prompt(), test_set_term_width(), test_set_auto_confirm(), 228test_with_dsym(), and test_with_dwarf(). We are using the default test loader 229from unittest framework, which uses the 'test' method name prefix to identify 230test methods automatically. 231 232This finishes the walkthrough of the test method test_set_output_path(self). 233Before we say goodbye, notice the little method definition at the top of the 234file: 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 241This is a classmethod (as shown by the @classmethod decorator) which allows the 242individual test class to perform cleanup actions after the test harness finishes 243with the particular test class. This is part of the so-called test fixture in 244the unittest framework. From http://docs.python.org/library/unittest.html: 245 246A test fixture represents the preparation needed to perform one or more tests, 247and any associate cleanup actions. This may involve, for example, creating 248temporary or proxy databases, directories, or starting a server process. 249 250The TestBase class uses such fixture with setUp(self), tearDown(self), 251setUpClass(cls), and tearDownClass(cls). And within teraDownClass(cls), it 252checks whether the current class has an attribute named 'classCleanup', and 253executes as a method if present. In this particular case, the classCleanup() 254calls a utility function system() defined in lldbtest.py in order to remove the 255files created by running the program as the tests are executed. 256 257This system() function uses the Python subprocess module to spawn the process 258and to retrieve its results. If the test instance passes the keyword argument 259pair 'sender=self', the detailed command execution through the operating system 260also gets recorded in a session object. If the test instance fails or errors, 261the session info automatically gets dumped to a file grouped under a directory 262named after the timestamp of the particular test suite run. 263 264For simple cases, look for the timestamp directory in the same directory of the 265test 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---------------------------------------------------------------------- 270Collected 6 tests 271 272test_set_auto_confirm (TestSettings.SettingsCommandTestCase) 273Test that after 'set auto-confirm true', manual confirmation should not kick in. ... ok 274test_set_output_path (TestSettings.SettingsCommandTestCase) 275Test that setting target.process.output-path for the launched process works. ... FAIL 276test_set_prompt (TestSettings.SettingsCommandTestCase) 277Test that 'set prompt' actually changes the prompt. ... ok 278test_set_term_width (TestSettings.SettingsCommandTestCase) 279Test that 'set term-width' actually changes the term-width. ... ok 280test_with_dsym (TestSettings.SettingsCommandTestCase) 281Test that run-args and env-vars are passed to the launched process. ... ok 282test_with_dwarf (TestSettings.SettingsCommandTestCase) 283Test that run-args and env-vars are passed to the launched process. ... ok 284 285====================================================================== 286FAIL: test_set_output_path (TestSettings.SettingsCommandTestCase) 287Test that setting target.process.output-path for the launched process works. 288---------------------------------------------------------------------- 289Traceback (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.") 292AssertionError: False is not True : 'stdout.txt' exists due to target.process.output-path. 293 294---------------------------------------------------------------------- 295Ran 6 tests in 8.219s 296 297FAILED (failures=1) 298/Volumes/data/lldb/svn/trunk/test $ ls 2010-10-19-14:10:49.059609 299 300NOTE: 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 304TestSettings.SettingsCommandTestCase.test_set_output_path.log 305/Volumes/data/lldb/svn/trunk/test $ 306 307We get one failure and a timestamp directory 2010-10-19-14:10:49.059609. 308For education purposes, the directory and its contents are reproduced here in 309the same directory as the current file. 310