Skip to content

Commit

Permalink
restructure testing framework (#123)
Browse files Browse the repository at this point in the history
- each testing/projects/{project} is dynamically imported
    - provides definitions for expected class / file hierarchies
    - hierarchies are deep copied, tests that reuse previously
      mutated these
    - docs updated to use automodule, except `cpp with spaces`
      (not possible to document in sphinx)
- ignore `from collections import MutableMapping` warnings
  (see also #119)
- skip mac builds on CI until #122 is solved
- install correct beautiful soup package in tox.ini
- fix url parsing in docs/testproject.py
  • Loading branch information
svenevs authored Nov 29, 2021
1 parent 359fccb commit 687cc71
Show file tree
Hide file tree
Showing 24 changed files with 696 additions and 492 deletions.
12 changes: 8 additions & 4 deletions .github/workflows/test_python.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,20 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
# os: [macos-latest, ubuntu-latest, windows-latest]
# TODO: either obtain doxygen 1.8.20 on mac, or find out why everything
# breaks in doxygen 1.9.2.
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v2
##########################################################################
- name: Install Doxygen (macOS)
if: contains(matrix.os, 'macos')
run: |
brew tap-new $USER/local-doxygen
brew extract --version=1.8.20 doxygen $USER/local-doxygen
HOMEBREW_NO_AUTO_UPDATE=1 brew install -v [email protected]
# brew tap-new $USER/local-doxygen
# brew extract --version=1.8.20 doxygen $USER/local-doxygen
# HOMEBREW_NO_AUTO_UPDATE=1 brew install -v [email protected]
HOMEBREW_NO_AUTO_UPDATE=1 brew install doxygen
- name: Install Doxygen (Ubuntu)
if: contains(matrix.os, 'ubuntu')
run: |
Expand Down
1 change: 1 addition & 0 deletions docs/testing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -152,5 +152,6 @@ Full Testing Suite Documentation
testing/decorators
testing/fixtures
testing/hierarchies
testing/projects
testing/utils
testing/tests
49 changes: 49 additions & 0 deletions docs/testing/projects.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
Testing Projects Module
========================================================================================

.. automodule:: testing.projects
:members:

`` testing.projects.c_maths`` Project
----------------------------------------------------------------------------------------

.. automodule:: testing.projects.c_maths
:members:

`` testing.projects.cpp with spaces`` Project
----------------------------------------------------------------------------------------

.. module:: testing.projects.cpp_with_spaces

This cannot be documented because it has spaces in the name and autodoc cannot
complete its import.

`` testing.projects.cpp_fortran_mixed`` Project
----------------------------------------------------------------------------------------

.. automodule:: testing.projects.cpp_fortran_mixed
:members:

`` testing.projects.cpp_func_overloads`` Project
----------------------------------------------------------------------------------------

.. automodule:: testing.projects.cpp_func_overloads
:members:

`` testing.projects.cpp_long_names`` Project
----------------------------------------------------------------------------------------

.. automodule:: testing.projects.cpp_long_names
:members:

`` testing.projects.cpp_nesting`` Project
----------------------------------------------------------------------------------------

.. automodule:: testing.projects.cpp_nesting
:members:

`` testing.projects.cpp_pimpl`` Project
----------------------------------------------------------------------------------------

.. automodule:: testing.projects.cpp_pimpl
:members:
6 changes: 5 additions & 1 deletion docs/testproject.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,16 @@ def run(self):
"Example: .. testproject:: c_maths"
)
self.content[0] = textwrap.dedent('''\
View the `{project} source code here <{baseurl}/{project_dir}>`_.
The ``{project}`` test project.
- Additional documentation: :mod:`testing.projects.{project_fixed}`.
- Source code for `{project} available here <{baseurl}/{project_dir}>`_.
See also: :data:`ExhaleTestCase.test_project <testing.base.ExhaleTestCase.test_project>`.
'''.format(
project=self.content[0],
project_dir=self.content[0].replace(" ", "\\ "),
project_fixed=self.content[0].replace(" ", "_"), # cpp with spaces project
baseurl=get_projects_baseurl()
))
project_node = testproject("\n".join(self.content))
Expand Down
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ filterwarnings =
# RemovedInSphinx30Warning: function based directive support is now deprecated.
# Use class based directive instead.
ignore::PendingDeprecationWarning:sphinx.util.docutils
# Any import of collections triggers this.
ignore: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3,and in 3.9 it will stop working

[coverage:run]
data_file = .coverage
Expand Down
20 changes: 20 additions & 0 deletions testing/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import shutil
import textwrap
import unittest
from importlib import import_module

import exhale
import pytest
Expand Down Expand Up @@ -226,6 +227,25 @@ def test_common(self):

attrs["test_common"] = test_common

# Import the default hierarchy dictionaries from the testing/projects folder
# and make it available to the class directly.
proj_mod = import_module(
"testing.projects.{test_project}".format(test_project=test_project))

default_class_hierarchy_dict = proj_mod.default_class_hierarchy_dict

def class_hierarchy_wrapper(self):
return default_class_hierarchy_dict()
attrs["class_hierarchy_dict"] = class_hierarchy_wrapper

default_file_hierarchy_dict = proj_mod.default_file_hierarchy_dict

def file_hierarchy_wrapper(self):
return default_file_hierarchy_dict()
attrs["file_hierarchy_dict"] = file_hierarchy_wrapper

attrs["test_project_module"] = proj_mod # In case it's ever needed...

# applying the default configuration override, which is overridden using the
# @confoverride decorator at class or method level
return default_confoverrides(
Expand Down
33 changes: 32 additions & 1 deletion testing/hierarchies.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import codecs
import os
import textwrap
from copy import deepcopy

from exhale.graph import ExhaleNode
from testing import get_exhale_root
Expand Down Expand Up @@ -375,6 +376,33 @@ def __init__(self, _type, name):
########################################################################################
# Doxygen index test classes (proxies to exhale.graph.ExhaleRoot). #
########################################################################################
def deep_copy_hierarchy_dict(spec):
"""
Produce a deep copy of the input specification of a hierarchy dictionary.
**Parameters**
``spec`` (:class:`python:dict`)
The input specification dictionary of the hierarchy.
**Returns**
A copy of the dictionary with new underlying objects.
"""
def traverse_copy(t_spec):
if isinstance(t_spec, dict):
t_spec_copy = {}
for s in t_spec:
v = t_spec[s]
s_copy = deepcopy(s)
if isinstance(v, dict):
t_spec_copy[s_copy] = traverse_copy(v)
else:
t_spec_copy[s_copy] = deepcopy(v)
return t_spec_copy
else:
return deepcopy(t_spec)
return traverse_copy(spec)


class root(object): # noqa: N801
"""
Represent a class or file hierarchy to simulate an :class:`exhale.graph.ExhaleRoot`.
Expand Down Expand Up @@ -417,7 +445,10 @@ def __init__(self, hierarchy_type, hierarchy):
self.top_level = []

# Initialize from the specified hierarchy and construct the graph.
self._init_from(hierarchy)
# NOTE: a deep copy of hierarchy is needed so that if a test wants to
# examine multiple tests against the same hierarchy dict (cpp_nesting)
# the manipulations here do not alter the original nodes.
self._init_from(deep_copy_hierarchy_dict(hierarchy))
self._reparent_all()

def _init_from(self, hierarchy):
Expand Down
3 changes: 3 additions & 0 deletions testing/projects/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
The test projects used to evaluate exhale.
"""
22 changes: 22 additions & 0 deletions testing/projects/c_maths/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""
The ``c_maths`` test project.
"""

from testing.hierarchies import directory, file, function, parameters


def default_class_hierarchy_dict():
"""Return the default class hierarchy dictionary."""
return {}


def default_file_hierarchy_dict():
"""Return the default file hierarchy dictionary."""
return {
directory("include"): {
file("c_maths.h"): {
function("int", "cm_add"): parameters("int", "int"),
function("int", "cm_sub"): parameters("int", "int")
}
}
}
25 changes: 25 additions & 0 deletions testing/projects/cpp with spaces/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""
The ``cpp with spaces`` test project.
"""

from testing.hierarchies import directory, file, function, namespace, parameters


def default_class_hierarchy_dict():
"""Return the default class hierarchy dictionary."""
return {}


def default_file_hierarchy_dict():
"""Return the default file hierarchy dictionary."""
return {
directory("include"): {
directory("with spaces"): {
file("with spaces.hpp"): {
namespace("with_spaces"): {
function("int", "value"): parameters()
}
}
}
}
}
43 changes: 43 additions & 0 deletions testing/projects/cpp_fortran_mixed/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""
The ``cpp_fortran_mixed`` test project.
"""

from testing.hierarchies import directory, file, function, namespace, parameters, variable


def default_class_hierarchy_dict():
"""Return the default class hierarchy dictionary."""
return {}


def default_file_hierarchy_dict():
"""Return the default file hierarchy dictionary."""
return {
directory("include"): {
directory("convert"): {
file("convert.hpp"): {
namespace("convert"): {
function("T", "to_degrees", template=["typename T"]): parameters("T"),
function("T", "to_radians", template=["typename T"]): parameters("T")
}
}
}
},
directory("src"): {
file("conversions.f90"): {
namespace("conversions"): {
variable("real(c_float)", "pi_s"): {},
variable("real(c_double)", "pi_d"): {},
variable("real(c_float)", "s_180"): {},
variable("real(c_double)", "d_180"): {},
# NOTE: function parameters in fortran are a little weird.
# 1. <type> has 'function', e.g. 'real(c_float) function'
# 2. Parameters are names, not types?
function("real(c_float) function", "degrees_to_radians_s"): parameters("degrees_s"),
function("real(c_double) function", "degrees_to_radians_d"): parameters("degrees_d"),
function("real(c_float) function", "radians_to_degrees_s"): parameters("radians_s"),
function("real(c_double) function", "radians_to_degrees_d"): parameters("radians_d")
}
}
}
}
68 changes: 68 additions & 0 deletions testing/projects/cpp_func_overloads/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""
The ``cpp_func_overloads`` test project.
"""

from testing.hierarchies import directory, file, function, namespace, parameters


def default_class_hierarchy_dict():
"""Return the default class hierarchy dictionary."""
return {}


def default_file_hierarchy_dict():
"""Return the default file hierarchy dictionary."""
return {
directory("include"): {
directory("overload"): {
file("overload.hpp"): {
function("int", "blargh"): parameters("int"),
namespace("overload"): {
# No args
function("void", "blargh"): parameters(),
# "pure" int overloads
function("int", "blargh"): parameters("int"),
function("int", "blargh"): parameters("int", "int"),
function("int", "blargh"): parameters("int", "int", "int"),
# "pure" float overloads
function("float", "blargh"): parameters("float"),
function("float", "blargh"): parameters("float", "float"),
function("float", "blargh"): parameters("float", "float", "float"),
# "pure" std::string overloads
function("std::string", "blargh"): parameters("const std::string&"),
function("std::string", "blargh"): parameters(
"const std::string&", "const std::string&"
),
function("std::string", "blargh"): parameters(
"const std::string&", "const std::string&", "const std::string&"
),
# absurd mixtures
function("std::size_t", "blargh"): parameters("std::size_t", "const std::string&"),
function("std::size_t", "blargh"): parameters(
"std::size_t", "const float&", "double", "const std::string&"
),
# vector overloads
function("void", "blargh"): parameters("std::vector<std::string>&"),
function("void", "blargh"): parameters("std::vector<std::vector<int>>&"),
# pointer style (spaces matter...)
function("void", "blargh"): parameters(
"const float *", "const float *", "float *", "std::size_t"
),
# templates
function("C::type", "blargh", template=["class C"]): parameters("typename C::type"),
# SFINAE is really pretty yeah?
function(
"std::enable_if<std::is_convertible<typename C::type, T>::value, T>::type",
"blargh",
template=["class C", "typename T"]
): parameters("typename C::type"),
function(
"std::enable_if<!std::is_convertible<typename C::type, T>::value, T>::type",
"blargh",
template=["class C", "typename T"]
): parameters("typename C::type")
}
}
}
}
}
Loading

0 comments on commit 687cc71

Please sign in to comment.