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.build() 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.build() statement is used to build a binary for this 100test instance. This will build the binary for the current debug info format. If 101we wanted to avoid running the test for every supported debug info format we 102could annotate it with @no_debug_info_test. The test would then only be run for 103the default format. The logic for building a test binary resides in the builder 104modules (packages/Python/lldbsuite/test/builders/builder.py) 105 106After the binary is built, it is time to specify the file to be used as the main 107executable by lldb: 108 109 # Construct the path to a file "a.out" inside the test's build folder. 110 exe = self.getBuildArtifact("a.out") 111 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) 112 113The runCmd() method is defined in the TestBase base class and its purpose is to 114pass the specified command to the lldb command interpreter. It's like you're 115typing the command within an interactive lldb session. 116 117The CURRENT_EXECUTABLE_SET is an assert message defined in the lldbtest module 118so that it can be reused from other test modules. 119 120By default, the runCmd() is going to check the return status of the command 121execution and fails the test if it is not a success. The assert message, in our 122case CURRENT_EXECUTABLE_SET, is used in the exception printout if this happens. 123 124There are cases when we don't care about the return status from the command 125execution. This can be accomplished by passing the keyword argument pair 126'check=False' to the method. 127 128After the current executable is set, we'll then execute two more commands: 129 130 # Set the output-path and verify it is set. 131 stdout = self.getBuildArtifact('stdout.txt') 132 self.runCmd("settings set target.process.output-path '%s'" %stdout) 133 self.expect("settings show target.process.output-path", 134 SETTING_MSG("target.process.output-path"), 135 startstr = "target.process.output-path (string) = '.*stdout.txt'") 136 137The first uses the 'settings set' command to set the static setting 138target.process.output-path to be 'stdout.txt', instead of the default 139'/dev/stdout'. We then immediately issue a 'settings show' command to check 140that, indeed, the setting did take place. Notice that we use a new method 141expect() to accomplish the task, which in effect issues a runCmd() behind the 142door and grabs the output from the command execution and expects to match the 143start string of the output against what we pass in as the value of the keyword 144argument pair: 145 146 startstr = "target.process.output-path (string) = '%s'" %stdout 147 148Take a look at TestBase.expect() within lldbtest.py for more details. Among 149other things, it can also match against a list of regexp patterns as well as a 150list of sub strings. And it can also perform negative matching, i.e., instead 151of expecting something from the output of command execution, it can perform the 152action of 'not expecting' something. 153 154This will launch/run the program: 155 156 self.runCmd("run", RUN_SUCCEEDED) 157 158And this asserts that the file 'stdout.txt' should be present after running the 159program. 160 161 # The 'stdout.txt' file should now exist. 162 self.assertTrue(os.path.isfile(stdout), 163 "stdout.txt' exists due to target.process.output-path.") 164 165Also take a look at main.cpp which emits some message to the stdout. Now, if we 166pass this assertion, it's time to examine the contents of the file to make sure 167it contains the same message as programmed in main.cpp: 168 169 # Read the output file produced by running the program. 170 with open(stdout, 'r') as f: 171 output = f.read() 172 173 self.expect(output, exe=False, 174 startstr = "This message should go to standard out.") 175 176We open the file and read its contents into output, then issue an expect() 177method. The 'exe=False' keyword argument pair tells expect() that don't try to 178execute the first arg as a command at all. Instead, treat it as a string to 179match against whatever is thrown in as keyword argument pairs! 180 181There are also other test methods present in the TestSettings.py mode: 182test_set_prompt(), test_set_term_width(), test_set_auto_confirm(), 183test_with_dsym(), and test_with_dwarf(). We are using the default test loader 184from unittest framework, which uses the 'test' method name prefix to identify 185test methods automatically. 186 187This finishes the walkthrough of the test method test_set_output_path(self). 188Before we say goodbye, notice the little method definition at the top of the 189file: 190 191 @classmethod 192 def classCleanup(cls): 193 system(["/bin/sh", "-c", "rm -f "+self.getBuildArtifact("output.txt")]) 194 system(["/bin/sh", "-c", "rm -f "+self.getBuildArtifact("stdout.txt")]) 195 196This is a classmethod (as shown by the @classmethod decorator) which allows the 197individual test class to perform cleanup actions after the test harness finishes 198with the particular test class. This is part of the so-called test fixture in 199the unittest framework. From http://docs.python.org/library/unittest.html: 200 201A test fixture represents the preparation needed to perform one or more tests, 202and any associate cleanup actions. This may involve, for example, creating 203temporary or proxy databases, directories, or starting a server process. 204 205The TestBase class uses such fixture with setUp(self), tearDown(self), 206setUpClass(cls), and tearDownClass(cls). And within teraDownClass(cls), it 207checks whether the current class has an attribute named 'classCleanup', and 208executes as a method if present. In this particular case, the classCleanup() 209calls a utility function system() defined in lldbtest.py in order to remove the 210files created by running the program as the tests are executed. 211 212This system() function uses the Python subprocess module to spawn the process 213and to retrieve its results. If the test instance passes the keyword argument 214pair 'sender=self', the detailed command execution through the operating system 215also gets recorded in a session object. If the test instance fails or errors, 216the session info automatically gets dumped to a file grouped under a directory 217named after the timestamp of the particular test suite run. 218 219For simple cases, look for the timestamp directory in the same directory of the 220test driver program dotest.py. For example, if we comment out the 221@expectedFailure decorator for TestSettings.py, and then run the test module: 222 223/Volumes/data/lldb/svn/trunk/test $ ./dotest.py -v settings 224---------------------------------------------------------------------- 225Collected 6 tests 226 227test_set_auto_confirm (TestSettings.SettingsCommandTestCase) 228Test that after 'set auto-confirm true', manual confirmation should not kick in. ... ok 229test_set_output_path (TestSettings.SettingsCommandTestCase) 230Test that setting target.process.output-path for the launched process works. ... FAIL 231test_set_prompt (TestSettings.SettingsCommandTestCase) 232Test that 'set prompt' actually changes the prompt. ... ok 233test_set_term_width (TestSettings.SettingsCommandTestCase) 234Test that 'set term-width' actually changes the term-width. ... ok 235test_with_dsym (TestSettings.SettingsCommandTestCase) 236Test that run-args and env-vars are passed to the launched process. ... ok 237test_with_dwarf (TestSettings.SettingsCommandTestCase) 238Test that run-args and env-vars are passed to the launched process. ... ok 239 240====================================================================== 241FAIL: test_set_output_path (TestSettings.SettingsCommandTestCase) 242Test that setting target.process.output-path for the launched process works. 243---------------------------------------------------------------------- 244Traceback (most recent call last): 245 File "/Volumes/data/lldb/svn/trunk/test/settings/TestSettings.py", line 125, in test_set_output_path 246 "'stdout.txt' exists due to target.process.output-path.") 247AssertionError: False is not True : 'stdout.txt' exists due to target.process.output-path. 248 249---------------------------------------------------------------------- 250Ran 6 tests in 8.219s 251 252FAILED (failures=1) 253/Volumes/data/lldb/svn/trunk/test $ ls 2010-10-19-14:10:49.059609 254 255NOTE: This directory name has been changed to not contain the ':' character 256 which is not allowed in windows platforms. We'll change the ':' to '_' 257 and get rid of the microsecond resolution by modifying the test driver. 258 259TestSettings.SettingsCommandTestCase.test_set_output_path.log 260/Volumes/data/lldb/svn/trunk/test $ 261 262We get one failure and a timestamp directory 2010-10-19-14:10:49.059609. 263For education purposes, the directory and its contents are reproduced here in 264the same directory as the current file. 265