Skip to content
Snippets Groups Projects
Commit e68077df authored by Lee McCuller's avatar Lee McCuller Committed by Jameson Rollins
Browse files

pytest and documentation

* pytest support: using my (moderately documented) canonical set of pytest fixtures
* documentation folder: boilerplate documentation and setup taken from dttxml, to be filled in a bit more primarily with apidoc using sphinx-autodoc
parent 4c39963b
No related branches found
No related tags found
1 merge request!126pytest and documentation
#for docs and setup.py outputs
build/
tresults/
# test cache
gwinc/test/cache
......
"""
Requires pytest to import
"""
import os
from os import path
import os
from shutil import rmtree
import contextlib
import pytest
_options_added = False
def pytest_addoption(parser):
global _options_added
#this check fixes issues if this gets run multiple times from sub conftest.py's
if _options_added:
return
else:
_options_added = True
parser.addoption(
"--plot",
action="store_true",
dest = 'plot',
help = "Have tests update plots (it is slow)",
)
parser.addoption(
"--do-stresstest",
action = "store_true",
help = "Run slow repeated stress tests"
)
parser.addoption(
"--no-preclear",
action="store_true",
default=False,
dest='no_preclear',
help="Do not preclear tpaths",
)
@pytest.fixture
def plot(request):
return request.config.getvalue('--plot')
return request.config.option.plot
@pytest.fixture
def tpath_preclear(request):
"""
Fixture that indicates that the test path should be cleared automatically
before running each test. This cleans up the test data.
"""
tpath_raw = tpath_raw_make(request)
no_preclear = request.config.getvalue('--no-preclear')
if not no_preclear:
rmtree(tpath_raw, ignore_errors = True)
@pytest.fixture
def tpath(request):
"""
Fixture that takes the value of the special test-specific folder for test
run data and plots. Usually the <folder of the test>/tresults/test_name/
"""
tpath_raw = tpath_raw_make(request)
os.makedirs(tpath_raw, exist_ok = True)
os.utime(tpath_raw, None)
return tpath_raw
@pytest.fixture
def tpath_join(request):
"""
Fixture that joins subpaths to the value of the special test-specific folder for test
run data and plots. Usually the <folder of the test>/tresults/test_name/.
This function should be use like test_thing.save(tpath_join('output_file.png'))
"""
tpath_raw = tpath_raw_make(request)
first_call = True
def tpath_joiner(*subpath):
nonlocal first_call
if first_call:
os.makedirs(tpath_raw, exist_ok = True)
os.utime(tpath_raw, None)
first_call = False
return path.join(tpath_raw, *subpath)
return tpath_joiner
@pytest.fixture
def fpath(request):
"""
py.test fixture that returns the folder path of the test being run. Useful
for accessing data files.
"""
return fpath_raw_make(request)
@pytest.fixture
def fpath_join(request):
"""
py.test fixture that runs :code:`os.path.join(path, *arguments)` to merge subpaths
with the folder path of the current test being run. Useful for referring to
data files.
"""
def join_func(*path):
return os.path.join(fpath_raw_make(request), *path)
return join_func
@pytest.fixture
def closefigs():
import matplotlib.pyplot as plt
yield
plt.close('all')
@pytest.fixture
def test_trigger():
"""
This fixture provides a contextmanager that causes a function to call
if an AssertionError is raised. It will also call if any of its argument,
or keyword arguments is true. This allows you to conveniently force
calling using other flags or fixtures.
The primary usage of this is to plot outputs only on test failures, while also
allowing plotting to happen using the plot fixture and pytest cmdline argument
"""
run_store = []
@contextlib.contextmanager
def fail(call, **kwargs):
run_store.append(call)
def call(did_fail):
do_call = did_fail
for k, v in kwargs.items():
if v:
do_call = True
break
if do_call:
for call in run_store:
call(fail = did_fail, **kwargs)
run_store.clear()
try:
yield
except AssertionError:
call(True)
raise
else:
call(False)
return
return fail
@pytest.fixture()
def ic():
"""
Fixture to provide icecream imports without requiring that the package exist
"""
try:
from icecream import ic
return ic
except ImportError:
pass
try:
from IPython.lib.pretty import pprint
return pprint
except ImportError:
from pprint import pprint
return pprint
#these are used with the pprint fixture
try:
import icecream
except ImportError:
icecream = None
pass
try:
from IPython.lib.pretty import pprint, pretty
pformat = pretty
except ImportError:
from pprint import pprint, pformat
@pytest.fixture
def pprint(request, tpath_join):
"""
This is a fixture providing a wrapper function for pretty printing. It uses
the icecream module for pretty printing, falling back to ipythons pretty
printer if needed, then to the python build in pretty printing module.
Along with printing to stdout, this function prints into the tpath_folder to
save all output into output.txt.
"""
fname = tpath_join('output.txt')
#pushes past the dot
print('---------------:{}:--------------'.format(request.node.name))
with open(fname, 'w') as F:
def pprint(*args, F = F, pretty = True, **kwargs):
outs = []
if pretty:
for arg in args:
outs.append(
pformat(arg)
)
else:
outs = args
if F is not None:
print(*outs, file = F)
if icecream is not None:
icecream.DEFAULT_OUTPUT_FUNCTION(' '.join(outs), **kwargs)
else:
print(*outs, **kwargs)
yield pprint
def tpath_raw_make(request):
if isinstance(request.node, pytest.Function):
return relfile_test(request.node.function.__code__.co_filename, request, 'tresults')
raise RuntimeError("TPath currently only works for functions")
def fpath_raw_make(request):
if isinstance(request.node, pytest.Function):
return os.path.split(request.node.function.__code__.co_filename)[0]
raise RuntimeError("TPath currently only works for functions")
def relfile(_file_, *args, fname = None):
fpath = path.split(_file_)[0]
post = path.join(*args)
fpath = path.join(fpath, post)
#os.makedirs(fpath, exist_ok = True)
#os.utime(fpath, None)
if fname is None:
return fpath
else:
return path.join(fpath, fname)
def relfile_test(_file_, request, pre = None, post = None, fname = None):
"""
Generates a folder specific to py.test function
(provided by using the "request" fixture in the test's arguments)
"""
if isinstance(pre, (list, tuple)):
pre = path.join(pre)
testname = request.node.name
if pre is not None:
testname = path.join(pre, testname)
if isinstance(post, (list, tuple)):
post = path.join(post)
if post is not None:
return relfile(_file_, testname, post, fname = fname)
else:
return relfile(_file_, testname, fname = fname)
......@@ -41,7 +41,7 @@ clean:
-rm -rf $(BUILDDIR)/*
livehtml:
sphinx-autobuild -i .#* -i *.pyc -i .*.swp -i .*.swo -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
sphinx-autobuild -z ../gwinc -i '*.#*' -i '*.pyc' -i '*.swp' -i '*.swo' -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
......
.wy-nav-content {
max-width: 1200px !important;
}
div.document {
width: auto;
margin: 30px auto 0 auto;
}
div.body{
max-width: 95%;
margin: 50px auto 0 auto;
}
API
################################################################################
.. _API:
.. py:module:: gwinc
......@@ -17,9 +17,15 @@
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
import os
import sys
#add the previous folder to the top of path, so that conftest.py can be found
#dirty but does the trick
sys.path.insert(0, os.path.abspath('..'))
#gwinc must be importable to build the docs properly anyway, using apidoc, so
#import it now for the __version__ parameter
import gwinc
# -- General configuration ------------------------------------------------
......@@ -33,11 +39,12 @@
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.todo',
'sphinx.ext.coverage',
#'sphinx.ext.todo',
#'sphinx.ext.coverage',
'sphinx.ext.mathjax',
'sphinx.ext.githubpages',
#'sphinx.ext.githubpages',
'sphinx.ext.napoleon',
'sphinx.ext.viewcode',
]
# Add any paths that contain templates here, relative to this directory.
......@@ -54,7 +61,7 @@ master_doc = 'index'
# General information about the project.
project = 'GWINC'
copyright = '2018, LIGO Laboratory'
copyright = '2021, LIGO Laboratory'
author = 'LIGO Laboratory'
# The version info for the project you're documenting, acts as replacement for
......@@ -62,7 +69,7 @@ author = 'LIGO Laboratory'
# built documents.
#
# The short X.Y version.
version = '0.0.0'
version = gwinc.__version__
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
......@@ -74,7 +81,7 @@ language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', '**.ipynb_checkpoints']
exclude_patterns = ['_build', ]
# The name of the Pygments (syntax highlighting) style to use.
#pygments_style = 'sphinx'
......@@ -84,9 +91,10 @@ pygments_style = 'default'
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
# Autodoc settings
autodoc_default_flags = ["members", "undoc-members"]
# -- Options for sourcelinks
srclink_project = 'https://git.ligo.org/gwinc/pygwinc'
srclink_src_path = 'gwinc'
srclink_branch = 'master'
......@@ -96,24 +104,41 @@ srclink_branch = 'master'
#useful for downloading the ipynb files
html_sourcelink_suffix = ''
html_title = 'GWINC docs'
html_short_title = 'GWINC docs'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
import sphinx_rtd_theme
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
#import sphinx_rtd_theme
#html_theme = "sphinx_rtd_theme"
#html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
#import jupyter_sphinx_theme
#html_theme = "jupyter_sphinx_theme"
#html_theme_path = [jupyter_sphinx_theme.get_html_theme_path()]
#html_theme = "alabaster"
html_theme = "alabaster"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
html_theme_options = dict(
description = "Gravitational Wave Interferometer Noise Calculator, to determine the fundamental limiting noises in interferometer designs",
extra_nav_links = {
'repository' : 'https://git.ligo.org/gwinc/pygwinc'
},
show_powered_by = False,
show_related = True,
#page_width = 'auto',
)
napoleon_type_aliases = {
#"CustomType": "mypackage.CustomType",
#"dict-like": ":term:`dict-like <mapping>`",
}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
......@@ -125,118 +150,24 @@ html_static_path = ['_static']
#
# This is required for the alabaster theme
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
#html_sidebars = {
# '**': [
# 'about.html',
# 'navigation.html',
# 'relations.html', # needs 'show_related': True theme option to display
# 'searchbox.html',
# ''
# #'donate.html',
# ]
#}
# Custom sidebar templates, maps document names to template names.
html_sidebars = {
'**': [
'localtoc.html',
'about.html',
#'globaltoc.html',
'navigation.html',
'relations.html',
'searchbox.html',
'srclinks.html',
],
'index': [
'globaltoc.html',
'navigation.html',
'relations.html',
'searchbox.html',
'srclinks.html',
],
]
}
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'GWINC'
html_logo = 'logo/LIGO_F0900035-v1.jpg'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(
master_doc,
'GWINC.tex',
'GWINC Documentation',
author,
'manual'
),
]
#http://www.sphinx-doc.org/en/master/usage/configuration.html#latex-options
#it appears that the LIGO docclass doesn't play well with all of the sphinx commands. Giving up for now
#latex_docclass = {
# 'manual' : 'ligodoc_v3',
#}
#latex_additional_files = [
# 'ligodoc_v3.tex'
#]
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(
master_doc,
'GWINC',
'GWINC Documentation',
[author],
1
)
]
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(
master_doc,
'GWINC',
'GWINC Documentation',
author,
'GWINC',
'One line description of project.',
'Miscellaneous'
),
]
#def setup(app):
# app.add_stylesheet('my_theme.css')
# app.add_stylesheet('pygments_adjust.css')
def setup(app):
app.add_stylesheet('my_theme.css')
#updates the coloring
#app.add_stylesheet('pygments_adjust.css')
Development
################################################################################
.. _development:
Testing with py.test
================================================================================
.. py:module:: conftest
py.test fixtures and setup
--------------------------------------------------------------------------------
.. autofunction:: plot
.. autofunction:: tpath_preclear
.. autofunction:: tpath
.. autofunction:: tpath_join
.. autofunction:: fpath
.. autofunction:: fpath_join
.. autofunction:: closefigs
.. autofunction:: test_trigger
.. autofunction:: ic
.. autofunction:: pprint
py.test setup hooks
--------------------------------------------------------------------------------
.. autofunction:: pytest_addoption
py.test helper functions
--------------------------------------------------------------------------------
.. autofunction:: tpath_raw_make
.. autofunction:: fpath_raw_make
.. autofunction:: relfile
.. autofunction:: relfile_test
Git setup for development
================================================================================
PyPI Releases
================================================================================
versioning
--------------------------------------------------------------------------------
......@@ -30,6 +30,7 @@ Contents
:maxdepth: 2
install
quickstart
api
dev
Install
################################################################################
.. _install:
docs/logo/LIGO_F0900035-v1.jpg

267 KiB

*.mat
.ipynb_checkpoints/
[pytest]
norecursedirs=_old fixme fix* out-*
python_files = test_*.py T*_*.py
python_functions = test_* T*
usefixtures = closefigs
[pytest-watch]
ignore = .*
"""
"""
import gwinc
from gwinc import load_budget
def test_load(pprint, tpath_join, fpath_join):
pprint(gwinc.IFOS)
for ifo in gwinc.IFOS:
B = load_budget(ifo)
trace = B.run()
fig = trace.plot()
fig.savefig(tpath_join('budget_{}.pdf'.format(ifo)))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment