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