Skip to content

Commit

Permalink
"README.rst" functional test.
Browse files Browse the repository at this point in the history
This commit adds a new optional functional test exercising the syntactic
validity of our front-facing "README.rst" file conditionally dependent
on "docutils", the reference reStructuredText (reST) parser. This test
is sufficiently expensive that we will probably *NOT* enable it under
continuous integration (CI), as doing so would begin exhausting our
precious GitHub Actions CI minutes. (*Extra extravagance of racy vagrancy!*)
  • Loading branch information
leycec committed Jan 29, 2021
1 parent b9ee253 commit 1c14c15
Show file tree
Hide file tree
Showing 12 changed files with 346 additions and 20 deletions.
19 changes: 15 additions & 4 deletions beartype/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,21 +207,32 @@ def _convert_version_str_to_tuple(version_str: str) -> tuple:
'''

# ....................{ METADATA ~ authors }....................
AUTHOR_EMAIL = 'leycec@gmail.com'
'''
Email address of the principal corresponding author (i.e., the principal author
responding to public correspondence).
'''


AUTHORS = 'Cecil Curry, et al.'
'''
Human-readable list of all principal authors of this package as a
comma-delimited string.
For brevity, this string *only* lists authors explicitly assigned copyrights.
For the list of all contributors regardless of copyright assignment or
attribution, see the top-level `AUTHORS.md` file.
attribution, see the top-level ``AUTHORS.md`` file.
'''


AUTHOR_EMAIL = 'leycec@gmail.com'
COPYRIGHT = '2014-2021 Cecil Curry'
'''
Email address of the principal corresponding author (i.e., the principal author
responding to public correspondence).
Legally binding copyright line excluding the license-specific prefix (e.g.,
``"Copyright (c)"``).
For brevity, this string *only* lists authors explicitly assigned copyrights.
For the list of all contributors regardless of copyright assignment or
attribution, see the top-level ``AUTHORS.md`` file.
'''

# ....................{ METADATA ~ urls }....................
Expand Down
37 changes: 34 additions & 3 deletions beartype/roar.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,21 @@ class BeartypeWarning(UserWarning, metaclass=_ABCMeta):
* At decoration time from the :func:`beartype.beartype` decorator.
* At call time from the new callable generated by the
:func:`beartype.beartype` decorator to wrap the original callable.
* At Sphinx-based documentation building time from Python code invoked by
the ``doc/Makefile`` file.
'''

pass


class BeartypeDependencyOptionalMissingWarning(BeartypeWarning):
'''
**Beartype missing optional dependency warning.**
This warning is emitted at various times to inform the user of a **missing
recommended optional dependency** (i.e., third-party Python package *not*
installed under the active Python interpreter whose installation is
technically optional but recommended).
'''

pass
Expand Down Expand Up @@ -505,6 +520,19 @@ class BeartypeDecorHintPepDeprecatedWarning(BeartypeDecorHintPepWarning):
#
# pass

# ....................{ WARNINGS ~ sphinx }....................
#FIXME: Consider removal.
# class BeartypeSphinxWarning(BeartypeWarning, metaclass=_ABCMeta):
# '''
# Abstract base class of all **beartype Sphinx warnings.**
#
# Instances of subclasses of this warning are emitted at Sphinx-based
# documentation building time from the ``doc/Makefile`` file in various edge
# cases warranting non-fatal warnings *without* raising fatal exceptions.
# '''
#
# pass

# ....................{ PRIVATE ~ decorator }....................
class _BeartypeDecorBeartypistryException(BeartypeDecorException):
'''
Expand Down Expand Up @@ -580,17 +608,20 @@ class _BeartypeUtilTextException(_BeartypeUtilException):

pass


class _BeartypeUtilKeyPoolException(_BeartypeUtilException):
'''
**Beartype key pool exception.**
This exception is raised by private functions of the private
:mod:`beartype._util.cache.pool.utilcachepool` subpackage when attempting
to call :meth:`release` on a non-existent object.
:mod:`beartype._util.cache.pool.utilcachepool` subpackage on various fatal
edge cases.
This exception denotes a critical internal issue and should thus *never* be
raised -- let alone allowed to percolate up the call stack to end users.
'''
pass


# ....................{ PRIVATE ~ util : call }..................
class _BeartypeCallHintRaiseException(
_BeartypeUtilException, metaclass=_ABCMeta):
Expand Down
13 changes: 13 additions & 0 deletions beartype_test/func/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.. # ------------------( SYNOPSIS )------------------
========================
Project Functional Tests
========================

This subpackage provides *all* pytest_-based **functional tests** (i.e.,
callables exercising the functional behaviour of this project *without* regard
to this project's public API) for this project.

.. # ------------------( LINKS )------------------
.. _pytest:
https://docs.pytest.org
Empty file added beartype_test/func/__init__.py
Empty file.
Empty file.
92 changes: 92 additions & 0 deletions beartype_test/func/doc/test_docreadme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env python3
# --------------------( LICENSE )--------------------
# Copyright 2014-2021 by Cecil Curry.
# See "LICENSE" for further details.

'''
**Project ``README.rst`` functional tests.**
This submodule functionally tests the syntactic validity of this project's
top-level ``README.rst`` file.
'''

# ....................{ IMPORTS }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# WARNING: To raise human-readable test errors, avoid importing from
# package-specific submodules at module scope.
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
from beartype_test.util.mark.pytskip import skip_unless_package

# ....................{ TESTS }....................
#FIXME: Consider submitting as a StackOverflow post. Dis iz l33t, yo!

# If the third-party "docutils" package satisfying this minimum version is
# unavailable, skip this test. Note that:
#
# * "docutils" is the reference standard for parsing reStructuredText (reST).
# Unsurprisingly, even Sphinx parses reST with "docutils".
# * This test makes assumptions about the "docutils" public API satisfied
# *ONLY* by this minimum version.
@skip_unless_package(package_name='docutils', minimum_version='0.15')
def test_doc_readme(monkeypatch) -> None:
'''
Functional test testing the syntactic validity of this project's top-level
``README.rst`` file by monkeypatching the public :mod:`docutils` singleton
responsible for emitting warnings and errors to instead convert these
warnings and errors into a test failure.
Parameters
----------
monkeypatch : MonkeyPatch
Builtin fixture object permitting object attributes to be safely
modified for the duration of this unit test.
'''

# Defer heavyweight imports.
from docutils.core import publish_parts
from docutils.utils import Reporter
from beartype_test.util.pytpath import get_repo_readme_file

# Decoded plaintext contents of this project's readme file as a string.
README_CONTENTS = get_repo_readme_file().read_text()

# List of all warning and error messages emitted by "docutils" during
# parsing of this project's top-level "README.rst" file.
system_messages = []

# Original non-monkey-patched method of the public :mod:`docutils`
# singleton emitting warnings and errors *BEFORE* patching this method.
system_message_unpatched = Reporter.system_message

def system_message_patched(reporter, level, message, *args, **kwargs):
'''
Method of the public :mod:`docutils` singleton emitting warnings and
errors redefined as a closure collecting these warnings and errors into
the local list defined above.
'''

# Call this non-monkey-patched method with all passed parameters as is.
message_result = system_message_unpatched(
reporter, level, message, *args, **kwargs)

# If this message is either a warning *OR* error, append this message
# to the above list.
if level >= reporter.WARNING_LEVEL:
system_messages.append(message)

# Return value returned by the above call as is.
return message_result

# Temporarily install this monkey-patch for the duration of this test.
monkeypatch.setattr(
Reporter,
name='system_message',
value=system_message_patched,
)

# Attempt to render this "README.rst" file as reST, implicitly invoking
# this monkey-patch.
publish_parts(source=README_CONTENTS, writer_name='html4css1')

# Assert "docutils" to have emitted *NO* warnings or errors.
assert not system_messages
13 changes: 13 additions & 0 deletions beartype_test/unit/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.. # ------------------( SYNOPSIS )------------------
==================
Project Unit Tests
==================

This subpackage provides *all* pytest_-based **unit tests** (i.e., callables
collectively exercising this project's public API but individually exercising
only a small subset of that API referred to as a "unit") for this project.

.. # ------------------( LINKS )------------------
.. _pytest:
https://docs.pytest.org
13 changes: 13 additions & 0 deletions beartype_test/util/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.. # ------------------( SYNOPSIS )------------------
======================
Project Test Utilities
======================

This subpackage provides pytest_-based **utilities** (i.e., general-purpose
lower-level pytest_ fixtures and related functions leveraged by higher-level
unit and functional tests) for this project's test suite.

.. # ------------------( LINKS )------------------
.. _pytest:
https://docs.pytest.org
131 changes: 131 additions & 0 deletions beartype_test/util/pytpath.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#!/usr/bin/env python3
# --------------------( LICENSE )--------------------
# Copyright 2014-2021 by Cecil Curry.
# See "LICENSE" for further details.

'''
**:mod:`pytest` **path** (i.e., directory and file) utilities.**
'''

# ....................{ IMPORTS }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# WARNING: To raise human-readable test errors, avoid importing from
# package-specific submodules at module scope.
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
from pathlib import Path
from beartype_test.util.pytroar import BeartypeTestPathException

# ....................{ GETTERS }....................
def get_repo_dir() -> Path:
'''
Concrete platform-agnostic :mod:`Path` object encapsulating this absolute
dirname of this project's **repository directory** (i.e., directory
containing both a ``.git/`` subdirectory and a subdirectory providing this
project's package) if found *or* raise an exception otherwise.
Returns
----------
Path
Concrete platform-agnostic object encapsulating the absolute dirname of
this project's repository directory.
Raises
----------
BeartypeTestPathException
If this path exists but is either:
* *Not* a directory.
* A directory *not* satisfying the expected filesystem structure.
FileNotFoundError
If this path does *not* exist.
RuntimeError
If this path exists but whose resolution to a physical path requires
resolving one or more cyclic symbolic links inducing an infinite loop.
'''
# print(f'current module paths: {__package__} [{__file__}]')

# Concrete platform-agnostic path encapsulating the absolute filename of
# current module.
MODULE_FILE = Path(__file__)

# Concrete platform-agnostic path encapsulating the absolute dirname of the
# package defining the current module.
MODULE_PACKAGE_DIR = MODULE_FILE.parent

# Concrete platform-agnostic path encapsulating the relative dirname of
# this project's repository directory relative to the dirname of the
# package defining the current module.
#
# Note that this path has *NOT* been validated to exist yet.
REPO_DIR_UNRESOLVED = MODULE_PACKAGE_DIR.joinpath('../..')

# Canonicalize this relative dirname into an absolute dirname if this path
# exists *OR* raise a "FileNotFoundError" or "RuntimeError" exception
# otherwise.
REPO_DIR = REPO_DIR_UNRESOLVED.resolve()

# If this path is *NOT* a directory, raise an exception.
if not REPO_DIR.is_dir():
raise BeartypeTestPathException(
f'Project repository path {REPO_DIR} not directory.')
# Else, this path is a directory.

# Concrete platform-agnostic path encapsulating the absolute dirname
# of the ".git/" subdirectory of this project's repository directory.
REPO_GIT_DIR = REPO_DIR.joinpath('.git')

# If this subdirectory either does *NOT* exist or is *NOT* a directory,
# raise an exception.
if not REPO_GIT_DIR.is_dir():
raise BeartypeTestPathException(
f'Project repository git subdirectory {REPO_GIT_DIR} not found.')
# Else, this subdirectory exists.

# Return this path.
return REPO_DIR


def get_repo_readme_file() -> Path:
'''
Concrete platform-agnostic :mod:`Path` object encapsulating the absolute
filename of this project's **readme file** (i.e., this project's
front-facing ``README.rst`` file) if found *or* raise an exception
otherwise.
Note that the :meth:`Path.read_text` method of this object trivially yields
the decoded plaintext contents of this file as a string.
Returns
----------
Path
Concrete platform-agnostic object encapsulating the absolute filename
of this project's readme file.
Raises
----------
BeartypeTestPathException
If this path exists but is *NOT* a file.
FileNotFoundError
If this path does *not* exist.
RuntimeError
If this path exists but whose resolution to a physical path requires
resolving one or more cyclic symbolic links inducing an infinite loop.
'''

# Concrete platform-agnostic path encapsulating this project's
# repository directory.
REPO_DIR = get_repo_dir()

# Concrete platform-agnostic path encapsulating the absolute filename of
# this project's readme file.
REPO_README_FILE = REPO_DIR.joinpath('README.rst')

# If this file either does *NOT* exist or is *NOT* a file, raise an
# exception.
if not REPO_README_FILE.is_file():
raise BeartypeTestPathException(
f'Project repository readme file {REPO_README_FILE} not found.')
# Else, this file exists.

# Return this path.
return REPO_README_FILE
15 changes: 13 additions & 2 deletions beartype_test/util/pytroar.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,24 @@ class BeartypeTestException(BeartypeException, metaclass=_ABCMeta):

pass

# ....................{ MARK }....................

class BeartypeTestMarkException(BeartypeTestException):
'''
**Beartype test mark exceptions.**
This exception is raised at test time from decorators defined by the
:mod:`beartype_test.util.mark` submodule.
:mod:`beartype_test.util.mark` subpackage.
'''

pass


class BeartypeTestPathException(BeartypeTestException):
'''
**Beartype test path exceptions.**
This exception is raised at test time from utility functions defined by the
:mod:`beartype_test.util.pytpath` submodule.
'''

pass

0 comments on commit 1c14c15

Please sign in to comment.