sphinxter.unittest

Module for testing examples.

If you have code examples in your documentation yourresource, like so:

2 + 2
# 4

You want to make sure they’re accurate. This module does exactly that. In your unittests, you can verify that the code will execute and even verify the values put in comments after a line match the value returned by that line.

All you need to do is extend the sphinxter.unittest.TestCase and use assertSphinxter:

import sphinxter.unittest

import yourmodule

class TestYourModule(sphinxter.unittest.TestCase):

    maxDiff = None

    def test_yourresource(self):

        self.assertSphinxter(yourmodule.yourresource)

That’ll look through the documentation, find execution example, and run them, compiling even the comments directly on the next line and verifying the match the line before.

class sphinxter.unittest.TestCase

Extends unittest.TestCase with asserts for testing examples

assertSphinxter(resource, evaluate=True)

Recurseivly asserts all Sections in documentation match their values using assertSphinxterSection and adding to the location to show where.

If the values in the comments are all the exact code, evaulate should be True.

If the values in the comments are all text, evaulate should be False.

If the values in the comments, vary, you can send a list or a dict.

If evaulate is a list, each call to assertSphinxterBlock, pops the next value to use off the front.

If evaulate is a dict, each call to assertSphinxterBlock will use the current location as the key to the location

Warning

This method pops values off the evaluate list. That means the evaluate value will be modified by this method.

Parameters:
  • resource (module or class or function or method) – Resource to assert has valid code exanmples

  • evaluate (bool or list or dict) – Whether values are to be eval’d

Usage

Given the follwing function is in the test.code module:

def depth():
    """
    deepme:
        evalme: |
            All eval'd::

                2 + 2
                # 4

                "Hello" + " " + "world"
                # "Hello world"
        mixme: |
            Lil of both::
                2 + 2
                # 4

                "\n".join(['1', '2', '3'])
                # 1
                # 2
                # 3
    """

You would test the whole thing like so:

import sphinxter.unittest

import test.code

class TestDepth(sphinxter.unittest.TestCase):

    def test_all(self):

        self.assertSphinxter(test.code.depth, evaluate={
            "deepme.evalme": True,
            "deepme.mixme": [True, False]
        })

Notice the evaluate argument. All off evalme is to be eval’d, so we just set that section to True.

In the mixme section, the first is tobe eval’d, the second not, so we just set that section to [True, False].

And we can see that it works:

import unittest

import test.test_code

unittest.TextTestRunner().run(unittest.makeSuite(test.test_code.TestDepth)).wasSuccessful()
# True
assertSphinxterBlock(block: sphinxter.unittest.Block, location: str = None, evaluate=True)

Asserts a Block of code matches its value using assertEqual and adding to the location to show where.

If the value in the comments are the exact code, evaulate should be True.

If the value in the comments are text, evaulate should be False.

The evaluate argument can also be a list, where the next evaluate value will be popped off.

The evaluate argument can also be a dict, where the evaluate value will be extracted use the location as a the key.

This is done to work with assertSphinxterSection and assertSphinxter.

Warning

This method pops values off the evaluate list. That means the evaluate value will be modified by this method.

Parameters:
  • block (Block) – Block to assert has valid code

  • location (str) – Comment to use with assertEqual, will be appended to with bad code location

  • evaluate (bool or list or dict) – Whether values are to be eval’d

Usage

Give the follwing function is in the test.code module:

def dual():
    """
    definition: |
        Here is some code with an examples that need to be eval'd::

            2 + 2
            # 4
    usage: |
        Here is some code with an example that needs to not be eval'd:::

            "\n".join(['1', '2', '3'])
            # 1
            # 2
            # 3
    """

You would test the blocka individually like so in the test.test_code module:

import sphinxter.unittest

import test.code

class TestDual(sphinxter.unittest.TestCase):

    maxDiff = None

    def test_definition(self):

        text = self.sphinxter(test.code.dual)["definition"]
        section = sphinxter.unittest.Section(text)
        self.assertSphinxterBlock(section.blocks[0])

    def test_usage(self):

        text = self.sphinxter(test.code.dual)["usage"]
        section = sphinxter.unittest.Section(text)
        self.assertSphinxterBlock(section.blocks[0], evaluate=False)

And we can see that it works:

import unittest

import test.test_code

unittest.TextTestRunner().run(unittest.makeSuite(test.test_code.TestDual)).wasSuccessful()
# True
assertSphinxterSection(section, location: str = None, evaluate=True)

Recurseivly asserts all Blocks in a Section match their values using assertSphinxterBlock and adding to the location to show where.

If the values in the comments are all the exact code, evaulate should be True.

If the values in the comments are all text, evaulate should be False.

If the values in the comments, vary, you can send a list or a dict.

If evaulate is a list, each call to assertSphinxterBlock, pops the next value to use off the front.

If evaulate is a dict, each call to assertSphinxterBlock will use the current location as the key to the location

Warning

This method pops values off the evaluate list. That means the evaluate value will be modified by this method.

Parameters:
  • section (Section or str or list or dict) – Section to assert has valid code

  • location (str) – Comment to use with assertEqual, will be appended to with bad code location

  • evaluate (bool or list or dict) – Whether values are to be eval’d

Usage

Give the follwing function is in the test.code module:

def mixing():
    """
    evalme: |
        Here is some code with an examples that need to be eval'd::

            2 + 2
            # 4
    leaveme: |
        Here is some code with an example that needs to not be eval'd:::

            "\n".join([1, 2, 3])
            # 1
            # 2
            # 3
    mixme: |
        Here's some code that has both eval no eval examples::

            2 + 2
            # 4

            "\n".join([1, 2, 3])
            # 1
            # 2
            # 3
    """

You would test each section individually like so:

import sphinxter.unittest

import test.code

class TestMixing(sphinxter.unittest.TestCase):

    maxDiff = None

    def test_evalme(self):

        text = self.sphinxter(test.code.mixing)["evalme"]
        section = sphinxter.unittest.Section(text)
        self.assertSphinxterSection(section)

    def test_leaveme(self):

        text = self.sphinxter(test.code.mixing)["leaveme"]
        section = sphinxter.unittest.Section(text)
        self.assertSphinxterSection(section, evaluate=False)

    def test_mixme(self):

        text = self.sphinxter(test.code.mixing)["mixme"]
        section = sphinxter.unittest.Section(text)
        self.assertSphinxterSection(section, evaluate=[True, False])

Notice the evaluate argument for mixme. The first block is to be evaluated, the second, not.

And we can see that it works:

import unittest

import test.test_code

unittest.TextTestRunner().run(unittest.makeSuite(test.test_code.TestMixing)).wasSuccessful()
# True
static sphinxter(resource) dict

Reads approximate documntation from any resource.

Warning

Do not use this method to generate documentation. The documentation is approximate since methods don’t know they’re part of a class but this is sufficient to make sure all executable code can be found and checked.

Parameters:

resource (module or class or function or method) – Resource to parse documentation for

Return type:

dict

Usage

Given the following function is in the test.code module:

def basic():
    """
    usage: |
        Here is some code with an examples that need to be eval'd::

            2 + 2
            # 4

        Even strings::

            "Hello" + " " + "world"
            # "Hello world"
    """

We can get the approximate documentation like so:

import sphinxter.unittest

import test.code

sphinxter.unittest.TestCase.sphinxter(test.code.basic)
# {
#     "kind": "function",
#     "name": "basic",
#     "signature": "()",
#     "usage": "Here is some code with an examples that need to be eval'd::\n\n    2 + 2\n    # 4\n\nEven strings::\n\n    \"Hello\" + \" \" + \"world\"\n    # \"Hello world\"\n"
# }
class sphinxter.unittest.Section(text: str)

Class for verifying example code

Parameters:

text (str) – The full section to parse and chunk

blocks: list[aphinxter.unittest.Block]

Comparable blocks in the code

code

The extraced executable postion of the section

static chunk(code)

Breaks code up into blocks by commented values to compare.

Parameters:

code – code

Usage

Given the function in test.code:

def basic():
    """
    usage: |
        Here is some code with an examples that need to be eval'd::

            2 + 2
            # 4

        Even strings::

            "Hello" + " " + "world"
            # "Hello world"
    """

This will just pull out the executable code from the usage secton:

import yaml
import sphinxter.unittest

import test.code

documentation = yaml.safe_load(test.code.basic.__doc__)
usage = sphinxter.unittest.Section.parse(documentation['usage'])
# 2 + 2
# # 4
#
# "Hello" + " " + "world"
# # "Hello world"
#

And this breaks it up into Blocks, that can each be evaluated:

blocks = sphinxter.unittest.Section.chunk(usage)

The first block is up to the equation value:

blocks[0].code
# 2 + 2

blocks[0].value
# 4

The second block includes the first, plus the concatenation:

blocks[1].code
# 2 + 2
#
# "Hello" + " " + "world"

blocks[1].value
# "Hello world"

In both cases, to validate usage, we can execuate the code and compare it to the value.

static parse(text)

Pulls all example code into a single block.

Parameters:

text – text

Usage

Given the function in test.code:

def basic():
    """
    usage: |
        Here is some code with an examples that need to be eval'd::

            2 + 2
            # 4

        Even strings::

            "Hello" + " " + "world"
            # "Hello world"
    """

This will just pull out the executable code from the usage secton:

import yaml
import sphinxter.unittest

import test.code

documentation = yaml.safe_load(test.code.basic.__doc__)
sphinxter.unittest.Section.parse(documentation['usage'])
# 2 + 2
# # 4
#
# "Hello" + " " + "world"
# # "Hello world"
#
class sphinxter.unittest.Block(code: list, value: list)

Class for storing a block, a pair for code to execute and (optional) value to compare to

Parameters:
  • code (list) – The code of the block

  • value (list) – The value of the block

code: str

The code of the block

value: str

The value of the block

valued: bool

Whether this block has a value

Only blocks with values in

eval(locals: dict)

Evaluates the value and returns it.

Parameters:

locals (dict) – locals vars already set

Raises:

CodeException – If any part of the value can’t be compiled or executed

exec(locals: dict)

Executes the code and returns the last value if valued

Parameters:

locals (dict) – locals vars already set

Raises:

CodeException – If any part of the code can’t be compiled or executed

exception sphinxter.unittest.CodeException(exception: Exception, code: str)

Exception for failed code.

Creates a message based on the full trace of the original exception plus the code that failed to execute, including line numbers.

This is to make it very clear when verifying examples what went wrong and where.

Parameters:
  • exception (Exception) – the original exception

  • code (str) – the code that failed