text: # -*- coding: utf8 -*-
  ########################################################################################
  # This file is part of exhale.  Copyright (c) 2017-2021, Stephen McDowell.             #
  # Full BSD 3-Clause license available here:                                            #
  #                                                                                      #
  #                https://github.com/svenevs/exhale/blob/master/LICENSE                 #
  ########################################################################################
  """
  The decorators module defines useful class / function decorators for test cases.
  """
  
  from __future__ import unicode_literals
  from copy import deepcopy
  from inspect import isclass
  
  import pytest
  
  from .utils import deep_update
  
  
  __all__ = ["default_confoverrides", "confoverrides", "no_run"]
  
  
  def _apply_confoverride_to_class(cls, config, priority):
      """
      Apply a configuration override ``config`` to class ``cls`` with a given priority.
  
      We need the priority trick as the default configuration is applied before a possible
      class-wide decorator, which should supersede the default configuration.  We use
      ``pytest.mark.exhale`` as a store of ``kwargs`` to apply to ``pytest.mark.sphinx``,
      and we use the priority to combine these kwargs with respect to priorities.
  
      This method performs the ultimate ``pytest.mark.sphinx``.
  
      **Parameters**
          ``cls`` (Subclass of :class:`testing.base.ExhaleTestCase`)
              The class to apply the ``confoverride`` to.
  
          ``config`` (:class:`python:dict`)
              The dictionary of ``confoverrides`` as would be supplied to
              ``pytest.mark.sphinx``.  These are the overrides to ``conf.py``.
  
          ``priority`` (:class:`python:int`)
              The positive integer indicating the priority of the new ``config`` updates,
              higher values take precedence over lower values.
  
              For example, when :func:`testing.decorators.default_confoverrides` calls
              this function, the priority is ``1``.  When the decorator
              :func:`testing.decorators.confoverrides` is applied to a class, the priority
              is ``2``.  Finally, when :func:`testing.decorators.confoverrides` is applied
              to a test function, its priority is ``3``.  Thus the function-level override
              has the highest priority, meaning any conflicting values with lower level
              priorities will lose out to the function-level override.
  
      **Return**
          Subclass of :class:`testing.base.ExhaleTestCase`
              The input ``cls`` is returned.
      """
      # look for test methods in the class
      for name, meth in cls.__dict__.items():
          if not callable(meth) or not name.startswith("test_"):
              continue
  
          # meth is a test method, let's go and mark it!
  
          # create marker kwargs
          kwargs = dict(confoverrides=deepcopy(config))
  
          # apply the exhale marker to the method, so that priority is saved
          meth = pytest.mark.exhale(priority, **kwargs)(meth)
  
          # create a list of exhale markers kwargs
          markers_kwargs = []
          if getattr(meth, "pytestmark", False):
              for mark in meth.pytestmark:
                  if mark.name == "exhale":
                      # mark.args[0]: the priority from pytest.mark.exhale
                      # mark.kwargs: the kwargs to override with said priority
                      markers_kwargs.append((mark.args[0], mark.kwargs))
  
          # sort that list according to priority
          markers_kwargs.sort(key=lambda m: m[0])
  
          # now we can generate the sphinx fixture kwargs by combining the above list of
          # kwargs depending on priority
          sphinx_kwargs = {}
          for __, kw in markers_kwargs:
              deep_update(sphinx_kwargs, kw)
  
          # and finally we set the sphinx markers with the combined kwargs, that override
          # the previous ones
          setattr(cls, name, pytest.mark.sphinx(**sphinx_kwargs)(meth))
  
      return cls
  
  
  def default_confoverrides(cls, config):
      """
      Apply the default configuration config to test class ``cls``.
  
      This configuration is set with a priority of ``1`` so that it may be overridden by
      the :func:`testing.decorators.confoverrides` decorator applied to test classes and
      functions.
  
      **Parameters**
          ``cls`` (Subclass of :class:`testing.base.ExhaleTestCase`)
              The class to apply the ``config`` dictionary as ``confoverrides`` to.
  
          ``config`` (:class:`python:dict`)
              The dictionary of ``confoverrides`` as would be supplied to
              ``pytest.mark.sphinx``.  These are the overrides to ``conf.py``.
  
      **Return**
          Subclass of :class:`testing.base.ExhaleTestCase`
              The input ``cls`` is returned.
      """
      return _apply_confoverride_to_class(cls, config, 1)
  
  
  def confoverrides(**config):
      """
      Override the defaults of |make_default_config| to supply to ``pytest.mark.sphinx``.
  
      .. |make_default_config| replace:: :func:`testing.base.make_default_config`
  
      It can be applied to a test method or a test class, which is equivalent to
      decorating every method in that class.
  
      Usage:
  
      .. code-block:: py
  
         @confoverrides(var1=value1, var2=value2)
         def test_something(self):
             ...
  
      or:
  
      .. code-block:: py
  
         @confoverrides(var1=value1, var2=value2)
         class MyTestCase(ExhaleTestCase):
             test_project = 'my_project'
             ...
  
      Typical usage of this decorator is to modify a value in ``exhale_args``, such as
  
      .. code-block:: py
  
         @confoverrides(exhale_args={"containmentFolder": "./alt_api"})
         def test_alt_out(self):
            ...
  
      However, this decorator can be used to change or set any value you would typically
      find in a ``conf.py`` file.
  
      **Parameters**
          ``**config`` (:class:`python:dict`)
              The dictionary of ``confoverrides`` as would be supplied to
              ``pytest.mark.sphinx``.  These are the overrides to ``conf.py``.
  
      **Return**
          (``class`` or :data:`python:types.FunctionType`)
              The decorated class or function.
      """
      def actual_decorator(meth_or_cls):
          if not config:
              return meth_or_cls
  
          if isclass(meth_or_cls):
              return _apply_confoverride_to_class(meth_or_cls, config, 2)
          else:
              return pytest.mark.exhale(3, confoverrides=config)(meth_or_cls)
  
      return actual_decorator
  
  
  def no_cleanup(method):
      """
      Prevent the "docs" directory and generated Doxygen / API from being deleted.
  
      Usage:
  
      .. code-block:: py
  
         class CMathsTests(ExhaleTestCase):
             # docs dir, generated API, and Doxygen will not be deleted so that you can
             # inspect what may be causing your test to fail
             @no_cleanup
             def test_being_developed(self):
                 pass
  
      .. danger::
  
         This decorator performs ``self.testroot = [self.testroot]`` as an internal bypass
         to the fixtures created in ``__new__`` for the metaclass.  Specifically, the
         fixtures generated check ``if isinstance(self.testroot, six.string_types)``.
  
         As such, since ``self.testroot`` may be desired in the given ``@no_cleanup``
         function, you must acquire it with ``testroot = self.testroot[0]``.  This is a
         hacky solution, but should be sufficient.  You have been warned.
  
      **Parameters**
          ``method`` (:data:`python:types.FunctionType`)
              **Must** be an instance-level testing function of a derived type of
              :class:`testing.base.ExhaleTestCase`.  The function should have only a
              single parameter ``self``.
  
      **Return**
          (:data:`python:types.FunctionType`)
              The decorated function, which simply calls the provided function and sets
              ``self.testroot = None``.  Click on ``[source]`` link for
              :func:`ExhaleTestCaseMetaclass.__new__ <testing.base.ExhaleTestCaseMetaclass.__new__>`
              and search for ``@no_cleanup`` to see how this prevents cleanup.
      """
      def actual_no_cleanup(self):
          self.testroot = [self.testroot]
          method(self)
      return actual_no_cleanup
  
  
  def no_run(obj):
      """
      Disable the generation of ``*.rst`` files in a specific test method.
  
      It can be applied to a test class, which will be equivalent to decorating every
      method in that class.
  
      Usage:
  
      .. code-block:: py
  
         @no_run
         def test_something(self):
             ...
  
      or:
  
      .. code-block:: py
  
         @no_run
         class MyTestCase(ExhaleTestCase):
             test_project = 'my_project'
             ...
  
      Internally this will use the :func:`testing.fixtures.no_run` fixture.
  
      **Parameters**
          ``obj`` (``class`` or :data:`python:types.FunctionType`)
              The class or function to disable exhale from generating reStructuredText
              documents for.
  
      **Return**
          ``class`` or :data:`python:types.FunctionType`
              The decorated ``obj``.
      """
      return pytest.mark.usefixtures("no_run")(obj)
  