From 26458261e34bc694e69c3ead66f4c9287a75cea0 Mon Sep 17 00:00:00 2001 From: Emily Dolson Date: Tue, 20 Jun 2023 00:56:36 -0400 Subject: [PATCH] Add testing documentation --- documentation/assets/BreatheFlowChart.svg | 4 + .../assets/BreatheFlowChart_DarkMode.svg | 4 + documentation/source/codeguide.rst | 163 +++++++++++++++++- 3 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 documentation/assets/BreatheFlowChart.svg create mode 100644 documentation/assets/BreatheFlowChart_DarkMode.svg diff --git a/documentation/assets/BreatheFlowChart.svg b/documentation/assets/BreatheFlowChart.svg new file mode 100644 index 00000000..58bb3522 --- /dev/null +++ b/documentation/assets/BreatheFlowChart.svg @@ -0,0 +1,4 @@ + + + +
Doxygen
Doxygen
Code
Code
Breathe
Parser
Breathe...
XML
XML
Breathe
Filter
Breathe...
Hierarchy of nested Python objects
Hierarchy of ne...
Sphinx
Sphinx
RST nodes
RST nodes
Final Document (HTML, TeX, etc.)
Final Document...
Breath
Renderer
Breath...
Python objects that actually need to be rendered
Python objects...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/documentation/assets/BreatheFlowChart_DarkMode.svg b/documentation/assets/BreatheFlowChart_DarkMode.svg new file mode 100644 index 00000000..77d09cf5 --- /dev/null +++ b/documentation/assets/BreatheFlowChart_DarkMode.svg @@ -0,0 +1,4 @@ + + + +
Doxygen
Doxygen
Code
Code
Breathe
Parser
Breathe...
XML
XML
Breathe
Filter
Breathe...
Hierarchy of nested Python objects
Hierarchy of ne...
Sphinx
Sphinx
RST nodes
RST nodes
Final Document (HTML, TeX, etc.)
Final Document...
Breath
Renderer
Breath...
Python objects that actually need to be rendered
Python objects...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/documentation/source/codeguide.rst b/documentation/source/codeguide.rst index 658de427..9c5179f9 100644 --- a/documentation/source/codeguide.rst +++ b/documentation/source/codeguide.rst @@ -4,6 +4,7 @@ How It Works ============ + There are three main sections to Breathe: parser, finders and renderers. Briefly: @@ -21,11 +22,36 @@ Briefly: object hierarchies rendering the objects, their children, their children's children and so on. Found in ``breathe.renderer``. +The following flow chart shows how the different components of Breathe transform +data. The shaded region indicates which components are part of Breathe. + +.. image:: ../assets/BreatheFlowChart.svg + :width: 500 + :alt: A flow chart showing that the initial input format is code. Doxgyen converts + code to XML. The Breathe parser converts XML to a hierarchy of python objects. + The Breathe Filter identifies which of these objects need to be rendered. The + Breathe Renderer converts these objects into reStructuredText (RST) nodes. + Finally, the RST node objects are passed to Sphinx to be turned into actual + HTML or LaTeX documents. + :class: only-light + :align: center + +.. image:: ../assets/BreatheFlowChart_DarkMode.svg + :width: 500 + :alt: A flow chart showing that the initial input format is code. Doxgyen converts + code to XML. The Breathe parser converts XML to a hierarchy of python objects. + The Breathe Filter identifies which of these objects need to be rendered. The + Breathe Renderer converts these objects into reStructuredText (RST) nodes. + Finally, the RST node objects are passed to Sphinx to be turned into actual + HTML or LaTeX documents. + :class: only-dark + :align: center + Parser ------ -The parsers job is to parse the doxygen xml output and create a hierarchy of +The parser's job is to parse the doxygen xml output and create a hierarchy of Python objects to represent the xml data. Doxygen XML Output @@ -230,3 +256,138 @@ code, the use of them is not something I've found very well documented and my code largely operates on a basis of trial and error. One day I'm sure I'll be enlightened, until then expect fairly naive code. + +Testing +------- + +Tests for Breathe can be found in the ``tests`` directory. They can be run by +running ``make test`` in your terminal (assuming that you have pytest installed). +The bulk of Breathe's test suite is in ``tests/test_renderer.py``, and this is +where any renderer-related tests should be added. This documentation will focus +on how to write more renderer tests, as this is the most common region of the code +to add new features to and perhaps the hardest to test. + +Creating Python Doxygen Nodes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +As indicated in the diagram at the top of this page, the renderer is expecting to +be run after the parser has created a hierarchy of python objects. Thus, there is +a lot of set-up that would usually happen before the renderer is invoked. For ease +of testing, it is often expedient to skip straight to the step where you have a +hierarchy of Python objects representing some hypothetical XML that doxygen could +have produced. + +``test_renderer.py`` contains a number of classes designed to assist with this +process. For just about any node that could show up in the XML produced by doxygen, +there is a class that quickly instantiates it in Python. For example, if you want +to test the rendering of a member definition, you can use the ``WrappedMemebrDef`` +class. Figuring out how nodes fit together can be challenging; until you're +comfortable with the type of XML produced by doxygen, the easiest process is likely: + +#. Write C++ code containing the behavior you would like to test. +#. Run Doxygen on it, which will produce an XML file (likely inside a directory + called xml within your doxygen output directory) +#. Re-build the relevant part of the xml file in Python using the ``Wrapped*`` + classes. + +For example, lets say you have a struct representing a cat. + +Your C++ might look something like this (inspired by Sy Brand's +`blog post `_): + +.. code-block:: cpp + + /** + A fluffy feline + */ + struct cat { + /** + Make this cat look super cute + */ + void make_cute(); + }; + +Running Doxygen on this might give you XML something like this: + +.. code-block:: xml + + + + + cat + test_cpp.hpp + + + void + void cat::make_cute + () + make_cute + cat::make_cute + + + + Make this cat look super cute + + + + + + + + + + A fluffy feline + + + + catmake_cute + + + + +There's a lot here. For now, let's just say we're testing something related to +member function definitions, and we only need to test that part of the +hierarchy. We can load the ``memberdef`` part of this XML into a +``WrappedMemberDef`` object as follows: + +.. code-block:: python + + member_def = WrappedMemberDef( + kind="function", # From "kind" in open memberdef tag + definition="void cat::make_cute", # From tag + type="void", # From tag + name="make_cute", # From tag + argstring="()", # From tag + virt="non-virtual", # From "virt" in open memberdef tag + ) + +As you can see, all of the arguments to the constructor are pulled directly out +of the XML, either from options on the original memberdef or tags nested under +it. There are a lot more optional arguments that can be provided to specify +additional details of the memberdef. + +More advanced hierarchies can be represented by nesting nodes inside each +other. For example, if our function took arguments, it would have ```` +tags nested within it. We could represent these as a list of ``WrappedParam`` +objects passed into the ``param`` keyword argument. + +To test that the node renders correctly, you can use the ``render`` function +provided in ``test_renderer.py``: + +.. code-block:: python + + # Render the node and grab its description + signature = find_node(render(app, member_def), "desc_signature") + # You can now examine the contents of signature.astext() and assert that + # they are as expected + + +Mocks +~~~~~ + +If you want to do more elaborate tests, it is useful to be aware of the various +Mock objects provided in ``test_renderer.py``. Because the renderer is +expecting to be executing in the context of a full Sphinx run, there are a lot +of objects that it is expecting to have access to. For example, rendering some +nodes requires making reference to a context object. The ``MockContext`` class +can serve as a stand-in. \ No newline at end of file