Testing Hierarchies Module

Various helper classes and functions for validating the class and file hierarchies.

Every derived class of testing.base.ExhaleTestCase should have at least one test case validating the class and file hierarchies. The class and file hierarchies do not need to be validated in the same method. In both cases, the recipe is:

  1. Create a hierarchy that enumerates all of the documented code in the test project. This will either be an instance of hierarchies.class_hierarchy, or of hierarchies.file_hierarchy.

  2. Call the comparison function for the created hierarchy: compare_class_hierarchy() or compare_file_hierarchy().

Representing Hierarchies

digraph G {
  rankdir="LR";
  ranksep=0.88;
  edge [dir=none];

  subgraph cluster_exhale {
    style=filled;
    color="#a2ddf9";
    label="Exhale Type";
    fontsize=22;
    node [style=filled, shape=rectangle, fontname="Courier"];
    e_fake_root_1 [style=none, shape=none, label=""];
    exhale_root [label="exhale.graph.ExhaleRoot"];
    e_fake_root_2 [style=none, shape=none, label=""];
  }

  subgraph cluster_testing {
    style=filled;
    color="#f7f389";
    label="Testing Types";
    fontsize=22;
    node [
      style=filled,
      shape=rectangle,
      fontname="Courier",
      width=2,
      fixedsize=true
    ];

    fake_root_1 [style=none, shape=none, label=""];
    hierarchy_root [label="hierarchies.root"];
    fake_root_2 [style=none, shape=none, label=""];
    file_hierarchy [label="file_hierarchy"];
    fake_hierarchy [style=none, shape=none, label=""];
    class_hierarchy [label="class_hierarchy"];


    hierarchy_root:e -> file_hierarchy:w;
    hierarchy_root -> fake_hierarchy [style=none, color=none];
    hierarchy_root:e -> class_hierarchy:w;

    {rank=same; fake_root_1; hierarchy_root; fake_root_2}
    {rank=same; class_hierarchy; fake_hierarchy; file_hierarchy}
  }

  exhale_root -> hierarchy_root [style=dashed, label="represents"];
}

During normal execution, exhale.graph.ExhaleRoot essentially represents index.xml coming from Doxygen. The index.xml enumerates far more than what the testing.hierarchies.root class represents, the primary goal for the class and file hierarchies is to validate generated (from Exhale) and expected (from the test) output with respect to

Name Scope Resolutions (testing.hierarchies.class_hierarchy)

Particularly, parent-child relationships constructed in exhale.graph.ExhaleRoot. See Class Hierarchies.

Parsed Directory Structure (testing.hierarchies.file_hierarchy)

Directory and file structure, and which file defines which documented construct. See File Hierarchies.

When creating a hierarchy to test, the author creates a json-like dictionary encoding the expected relationships. Generally, every helper class maps to a nested dictionary value, with some exceptions:

digraph G {
  rankdir="LR";
  ranksep=0.88;
  edge [dir=none];

  subgraph cluster_exhale {
    style=filled;
    color="#a2ddf9";
    label="Exhale Type";
    fontsize=22;
    node [style=filled, shape=rectangle, fontname="Courier"];
    subgraph cluster_hacksize {
     label=" ";// soooo hacky...
     fontsize=18;

    fake_node_1 [style=none, shape=none, label=""];
    fake_node_2 [style=none, shape=none, label=""];
    fake_node_3 [style=none, shape=none, label=""];
    fake_node_4 [style=none, shape=none, label=""];
    exhale_node [label="exhale.graph.ExhaleNode"];
    fake_node_5 [style=none, shape=none, label=""];
    fake_node_6 [style=none, shape=none, label=""];
    fake_node_7 [style=none, shape=none, label=""];
    fake_node_8 [style=none, shape=none, label=""];
    fake_node_9 [style=none, shape=none, label=""];
    }
  }

  subgraph cluster_testing {
    style=filled;
    color="#f7f389";
    label="Testing Types";
    fontsize=22;
    node [
      style=filled,
      shape=rectangle,
      fontname="Courier",
      width=2,
      fixedsize=true
    ];

    hierarchy_node [label="hierarchies.node"];

    subgraph cluster_keys {
      style=filled;
      color=cadetblue;
      label="Key Type";
      fontsize=18;

      clike;
      define;
      directory;
      enum;
      file;
      function;
      namespace;
      typedef;
      union;
      variable;
    }

    subgraph cluster_values {
      style=filled;
      color=mediumaquamarine;
      label="Value Type";
      fontsize=18;

      v_clike     [label="dict"];
      v_define    [label="????"];
      v_directory [label="dict"];
      v_enum      [label="????"];
      v_file      [label="dict"];
      v_function  [label="parameters"];
      v_namespace [label="dict"];
      v_typedef   [label="????"];
      v_union     [label="dict"];
      v_variable  [label="????"];
    }

    hierarchy_node -> clike:w;
    hierarchy_node -> define:w;
    hierarchy_node -> directory:w;
    hierarchy_node -> enum:w;
    hierarchy_node -> file:w;
    hierarchy_node -> function:w;
    hierarchy_node -> namespace:w;
    hierarchy_node -> typedef:w;
    hierarchy_node -> union:w;
    hierarchy_node -> variable:w;

    clike     -> v_clike [dir=forward];
    define    -> v_define [dir=forward];
    directory -> v_directory [dir=forward];
    enum      -> v_enum [dir=forward];
    file      -> v_file  [dir=forward];
    function  -> v_function  [dir=forward];
    namespace -> v_namespace [dir=forward];
    typedef   -> v_typedef [dir=forward];
    union     -> v_union [dir=forward];
    variable  -> v_variable [dir=forward];


  }

  exhale_node -> hierarchy_node [dir=none];
  fake_node_1 -> clike [style=none, color=none];
}

Todo

???? values indicate items not currently implemented or used in the testing framework.

class testing.hierarchies.root(hierarchy_type, hierarchy)[source]

Represent a class or file hierarchy to simulate an exhale.graph.ExhaleRoot.

Parameters
hierarchy_type (str)

May be either "class" or "file", indicating which type of hierarchy is being represented.

hierarchy (dict)

The hierarchy dictionary, see reference documentation for class_hierarchy and / or file_hierarchy for examples.

Raises
ValueError

If hierarchy_type is neither "class" nor "file", or the specified hierarchy not a dictionary or malformed.

toConsole()[source]

Dump the hierarchy to the console.

Calls node.toConsole for each hierarchies.node in self.top_level.

Class Hierarchies

class testing.hierarchies.class_hierarchy(hierarchy)[source]

Represent a name scope hierarchy.

The class hierarchy represents things that in C++ would equate to using a :: to gain access to. This includes:

Consider the following C++ code:

// in file: include/main.h
#pragma once

namespace detail {
    struct SomeStruct { /* ... */ };
}

struct SomeStruct {
    struct View { /* ... */ };
};

Then the testing code may look like:

from testing.base import ExhaleTestCase
from testing.hierarchies import class_hierarchy,         \
                                clike,                   \
                                compare_class_hierarchy, \
                                namespace

class SomeTest(ExhaleTestCase):
    test_project = "..." # specify the correct name...

    def test_class_hierarchy(self):
        class_hierarchy_dict = {
            clike("struct", "SomeStruct"): {
                clike("struct", "View"): {}
            },
            namespace("detail"): {
                clike("struct", "SomeStruct"): {}
            }
        }
        compare_class_hierarchy(self, class_hierarchy(class_hierarchy_dict))
Parameters
hierarchy (dict)

The hierarchy associated with the name scopes for the test project.

testing.hierarchies.compare_class_hierarchy(test, test_root)[source]

Compare the parsed and expected class hierarchy for the specified test.

This method should only be called in a test_* method implemented in a ExhaleTestCase member function.

Parameters
test (ExhaleTestCase)

The test instance. This test will have its assert* methods called in this method. The exhale.graph.ExhaleRoot instance for the test project is acquired through this parameter.

test_root (class_hierarchy)

The class hierarchy to compare the parsed root with.

Raises
ValueError

When test is not an ExhaleTestCase, or test_root is not a class_hierarchy.

File Hierarchies

class testing.hierarchies.file_hierarchy(hierarchy)[source]

Represent a parsed directory structure, including which file defines which compound.

Note

The test case file hierarchies encompass more than just what should show up in the generated Exhale file hierarchy (which only includes directories and files).

Specifically, the test file hierarchy is expected to encode every documented object in the project. This means there will be duplicated constructs between the class and file hierarchy tests.

This is done in order to help check that the file a construct was defined in is correctly parsed / generated by Exhale.

Working with the same example code as above:

// in file: include/main.h
#pragma once

namespace detail {
    struct SomeStruct { /* ... */ };
}

struct SomeStruct {
    struct View { /* ... */ };
};

The testing code is essentially the same dictionary, only we need to include directory and file information:

from testing.base import ExhaleTestCase
from testing.hierarchies import clike,                  \
                                compare_file_hierarchy, \
                                directory,              \
                                file                    \
                                file_hierarchy          \
                                namespace

class SomeTest(ExhaleTestCase):
    test_project = "..." # specify the correct name...

    def test_file_hierarchy(self):
        file_hierarchy_dict = {
            directory("include"): {
                file("main.h"): {
                    clike("struct", "SomeStruct"): {
                        clike("struct", "View"): {}
                    },
                    namespace("detail"): {
                        clike("struct", "SomeStruct"): {}
                    }
                }
            }
        }
        compare_file_hierarchy(self, file_hierarchy(file_hierarchy_dict))
Parameters
hierarchy (dict)

The hierarchy associated with the name scopes for the test project.

testing.hierarchies.compare_file_hierarchy(test, test_root)[source]

Compare the parsed and expected file hierarchy for the specified test.

This method should only be called in a test_* method implemented in a ExhaleTestCase member function.

Parameters
test (ExhaleTestCase)

The test instance. This test will have its assert* methods called in this method. The exhale.graph.ExhaleRoot instance for the test project is acquired through this parameter.

test_root (file_hierarchy)

The class hierarchy to compare the parsed root with.

Raises
ValueError

When test is not an ExhaleTestCase, or test_root is not a file_hierarchy.

Utility Classes

class testing.hierarchies.node(name, kind)[source]

Testing hierarchy parent class for pass-through construction of exhale.graph.ExhaleNode.

Upon construction, the parent class’s refid parameter is set as the empty string in the testing framework. This item is the Doxygen hash value, which is not available until after Doxygen has been executed.

Parameters
name (str)

The name of the documented compound.

kind (str)

Assumed to be one of the types in exhale.utils.AVAILABLE_KINDS.

__repr__()[source]

Return ExhaleNode.__repr__, possibly manufacturing self.template_params.

A dirty hack to piggy-back off of ExhaleNode’s __repr__, the same value is returned but for anything with a template we need to coerce the ExhaleNode.template_params.

When parsing from doxygen you get:

((refid, typed), declared_name, defined_name)

But in the testing framework we’re just using lists of strings. Hallucinate everything as the typeid as far as ExhaleNode is concerned.

Todo

On a day that is not this day, make ExhaleNode.template_params use a class Tparam and reuse that here and in the testing framework. And just rename it to template, unifying with kind=function, and have the testing framework use the same variable name. But that’s a lot of effort right now and I just want good debugging prints.

toConsole(level)[source]

Print this node to the console, and call toConsole for all children.

Logging is done to sys.stdout.

Parameters
level (int)

The recursion level, used as "  " * level to indent children.

Classes and Structs

class testing.hierarchies.clike(kind, name, template=[])[source]

Represent a class or struct.

Parameters
kind (str)

Assumed to be either "class" or "struct".

name (str)

The un-qualified name of the class or struct being represented.

template (???)

Todo

template specification / creation TBD for classes / structs.

Directories

class testing.hierarchies.directory(name)[source]

Represent a directory in a file hierarchy.

Note

This class may only appear in a file hierarchy, not a class hierarchy.

Parameters
name (str)

The name of the directory being represented.

Enums

class testing.hierarchies.enum(name, values=None)[source]

Represent an enum.

Parameters
name (str)

The name of the enum being represented.

values (???)

Todo

enumvalues are not currently handled in Exhale proper.

Files

class testing.hierarchies.file(name)[source]

Represent a file.

Note

This class may only appear in a file hierarchy, not a class hierarchy.

Parameters
name (str)

The name of the file being represented.

Functions

class testing.hierarchies.function(return_type, name, template=None)[source]

Represent a (partial) function.

Note

This key must always map to a value of hierarchies.parameters.

function("int", "add"): parameters("int", "int")

represents the function declaration

int add(int a, int b);

Note that parameter names (a and b in this example) are not to be included, only parameter types.

Parameters
return_type (str)

The return type of the function, e.g. "void" or "int".

name (str)

The name of the function.

template (???)

Todo

template specification / creation TBD for functions.

setParameters(parameters)[source]

Set the parameters of this function.

Since this class is to map to a value of type hierarchies.parameters, when the dictionary is being parsed this method will be called.

class testing.hierarchies.parameters(*args)[source]

Represent a hierarchies.function parameters.

Parameters
*args (Parameter Pack)

Arbitrary list, assumed to be all strings. For example,

int add(int a, int b);

is represented by creating

function("int", "add"): parameters("int", "int")

whereas

void serialize(Serializer &s, const std::string &name, int id);

is represented by creating

function("void", "serialize"): parameters(
    "Serializer&",
    "const std::string&",
    "int"
)

Only parameter types are to be included, not declared names (int, int not int a, int b).

Attributes
self.args (list of str)

The in-order list of function arguments including types.

__repr__()[source]

Return ", ".join(a for a in self.args).

Namespaces

class testing.hierarchies.namespace(name)[source]

Represent a namespace.

Parameters
name (str)

The name of the namespace being represented.

Unions

class testing.hierarchies.union(name)[source]

Represent a union.

Todo

union members are not actually tested at this point.

Parameters
name (str)

The name of the union being represented.