Index: lldb/docs/resources/test.rst =================================================================== --- lldb/docs/resources/test.rst +++ lldb/docs/resources/test.rst @@ -1,27 +1,156 @@ Testing ======= +.. contents:: + :local: + +Test Suite Structure +-------------------- + The LLDB test suite consists of three different kinds of test: -* Unit test. These are located under ``lldb/unittests`` and are written in C++ - using googletest. -* Integration tests that test the debugger through the SB API. These are - located under ``lldb/packages/Python/lldbsuite`` and are written in Python - using ``dotest`` (LLDB's custom testing framework on top of unittest2). -* Integration tests that test the debugger through the command line. These are - located under `lldb/test/Shell` and are written in a shell-style format - using FileCheck to verify its output. +* **Unit tests**: written in C++ using the googletest unit testing library. +* **Shell tests**: Integration tests that test the debugger through the command + line. These tests interact with the debugger either through the command line + driver or though ``lldb-test`` which is binary that exposes the internal data + structures in an easy-to-parse way for testing. Most people will know these + as *lit tests* in LLVM, although lit is the test driver and ShellTest is the + test format that uses ``RUN:`` lines. ``FileCheck`` is used to verify the + output. +* **API tests**: Integration tests that interact with the debugger through the + SB API. These are are written in Python and use LLDB's ``dotest.py`` testing + framework on top of Python's unittest2. + +All three test suites use ``lit`` (`LLVM Integrated Tester +`_ ) as the test driver. The test +suites can be run as a whole or separately. + + +Unit Tests +`````````` + +Unit tests are localted under ``lldb/unittests``. If it's possible to test +something in isolation or as a single unit, you should make it a unit test. + +Often you need instances of the core objects such as a debugger, target or +process, in order to test something meaningful. We already have a handful of +tests that have the necessary boiler plate, but this is something we could +abstract away and make it more user friendly. + +Shell Tests +``````````` + +Shell tests are located under ``lldb/test/shell``. These tests are generally +build around checking the output of ``lldb`` (the command line driver) or +``lldb-test`` using ``FileCheck``. Shell tests are generally small and fast to +write because they require little boilerplate. + +``lldb-test`` is a relatively new addition to the test suite. It was the first +tool that was added that is designed for testing. Since then it has been +continuously extended with new subcommands, improving our test coverage. Among +other things you can use it to query lldb for symbol files, for object files +and breakpoints. + +Obviously shell tests are great for testing the command line driver itself or +the subcomponents already exposed by lldb-test. But when it comes to LLDB's +vast functionality, most things can be tested both through the driver as well +as the Python API. For example, to test setting a breakpoint, you could do it +from the command line driver with ``b main``` or you could use the SB API and do +something like ``target.BreakpointCreateByName``. + +A good rule of thumb is to prefer shell tests when what is being tested is +relatively simple. Expressivity is limited compared to the API tests, which +means that you have to have a well-defined test scenario that you can easily +match with ``FileCheck``. + +Another thing to consider are the binaries being debugged, which we call +inferiors. For shell tests, they have to be relatively simple. The +``dotest.py`` test framework has extensive support for complex build scenarios, +while shell tests are limited to single lines of shell commands with compiler +and linker invocations. + +Finally, the shell tests always run in batch mode. You start with some input +and the test verifies the output. The debugger can be sensitive to its +environment, such as the the platform it runs on. It can be hard to express +that the same test might behave slightly differently on macOS and Linux. +Additionally, the debugger is an interactive tool, and the shell test provide +no good way of testing those interactive aspects, such as tab completion for +example. + +API Tests +````````` + +API tests are located under ``lldb/test/API``. Thy are run with the +``dotest.py``. Tests are written in Python and test binaries (inferiors) are +compiled with Make. + +Below is an example of what an elementary API test subdirectory might look +like. It will always contain a python file, starting with ``Test``. Most of +the tests are structured as a binary begin debugged, so there will be one or +more sources file and a Makefile. -All three test suites use the `LLVM Integrated Tester -`_ (lit) as their test driver. The -test suites can be run as a whole or separately. +:: -Many of the tests are accompanied by a C (C++, ObjC, etc.) source file. Each -test first compiles the source file and then uses LLDB to debug the resulting -executable. + sample_test + ├── Makefile + ├── TestSampleTest.py + └── main.c + +Let's start with the Python test file. Every test is its own class and can have +one or more test methods, that start with ``test_``. Many tests define +multiple test methods and share a bunch of common code. For example, for a +fictive tests that makes sure we can set breakpoints we might have one test +method that ensures we can set a breakpoint by address, on that sets a +breakpoint by name and another that sets the same breakpoint by file and line +number. The setup, teardown and everything else other than setting the +breakpoint could be shared. + +Our testing framework also has a bunch of utilities that abstract common +operations, such as creating targets, setting breakpoints etc. When code is +shared across tests, we extract it into a utility in ``lldbutil``. It's always +worth taking a look at lldbutil to see if there's a utility to simplify some +of the testing boiler plate. Because we can't always audit every existing test, +this is double true when looking at an existing test for inspiration. + +It's possible to skip or XFAIL tests using decorators. You'll see them a lot. +The debugger can be sensitive to things like the architecture, the host and +target platform, the compiler version etc. LLDB comes with a range of +predefined decorators for these configurations. Another great thing about these +decorators is that they're very easy to extend, it's even possible to define a +function in a test case that determines whether the test should be run or not. + +In addition to providing a lot more flexibility when it comes to writing the +test, the API test also allow for much more complex scenarios when it comes to +building inferiors. Every tests has its own Makefile, most of them only a few +lines long. A shared Makefile (``Makefile.rules``) with about a thousand lines +of rules takes care of most if not all of the boiler plate, while individual +make files can be used to build more advanced tests. 
 + +Another things this enables is having different variants for the same test +case. By default, we run every test for all 3 debug info formats, so once with +DWARF from the object files, once with gmodules and finally with a dSYM on +macOS or split DWARF (DWO) on Linux. But there are many more things we can test +that are orthogonal to the test itself. On GreenDragon we have a matrix bot +that runs the test suite under different configurations, with older host +compilers and different DWARF versions. + +As you can imagine, this quickly lead to combinatorial explosion in the number +of variants. It's very tempting to add more variants because it's an easy way +to increase test coverage. It doesn't scale. It's cheap in developer time, but +extremely expensive in terms of runtime and maintenance time because it +multiplies the overhead. + +The key take away is that the different variants don't prevent the need for +focused tests. So relying on it to test say DWARF5 is a really bad idea. +Instead you should write tests that check the specific DWARF5 feature, and +have the variant as a nice-to-have. + +In conclusion, you'll want to opt for an API test to test the API itself or +when you need the expressively, either for the test case itself or for the +program being debugged. The fact that the API tests work with different +variants mean that more general tests should be API tests, so that they can be +run against the different variants. -.. contents:: - :local: Running The Tests -----------------