Implements infrastructure for doctesting Pint
The most important aspect is a custom OutputChecker used to circumvent
certain aspects of an easy to read documentation. It performs a series
of conversions to the actual and expected output string of each doctest.
If the standard checker fails, the following steps are taken stopping
when the first is successful:
1. eval the strings and compare the results.
(this is useful to compare unordered collections such as dicts)
2. parse the repr of a Quantity object and compare values and units
Importantly, values are compared allowing rounding errors (0.1% difference)
3. parse the str repr of a Quantity object and compare values and units
Again, values are compared allowing rounding errors (0.1% difference)
4. Replace the repr of a Unit by the corresponding string and compare
the resulting strings.
5. If at least one replacement was done in 4, then start in 1 again.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import division, unicode_literals, print_function, absolute_import
|
||||
|
||||
import doctest
|
||||
import os
|
||||
import logging
|
||||
from contextlib import contextmanager
|
||||
@@ -10,6 +11,7 @@ from pint.compat import ndarray, unittest, np
|
||||
|
||||
from pint import logger, UnitRegistry
|
||||
from pint.quantity import _Quantity
|
||||
from pint.testsuite.helpers import PintOutputChecker
|
||||
from logging.handlers import BufferingHandler
|
||||
|
||||
|
||||
@@ -117,7 +119,9 @@ class QuantityTestCase(BaseTestCase):
|
||||
def testsuite():
|
||||
"""A testsuite that has all the pint tests.
|
||||
"""
|
||||
return unittest.TestLoader().discover(os.path.dirname(__file__))
|
||||
suite = unittest.TestLoader().discover(os.path.dirname(__file__))
|
||||
#add_docs(suite)
|
||||
return suite
|
||||
|
||||
|
||||
def main():
|
||||
@@ -137,3 +141,41 @@ def run():
|
||||
test_runner = unittest.TextTestRunner()
|
||||
return test_runner.run(testsuite())
|
||||
|
||||
|
||||
|
||||
import math
|
||||
|
||||
_GLOBS = {
|
||||
'wrapping.rst': {
|
||||
'pendulum_period': lambda length: 2*math.pi*math.sqrt(length/9.806650),
|
||||
'pendulum_period2': lambda length, swing_amplitude: 1.,
|
||||
'pendulum_period_maxspeed': lambda length, swing_amplitude: (1., 2.),
|
||||
'pendulum_period_error': lambda length: (1., False),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def add_docs(suite):
|
||||
"""Add docs to suite
|
||||
|
||||
:type suite: unittest.TestSuite
|
||||
"""
|
||||
docpath = os.path.join(os.path.dirname(__file__), '..', '..', 'docs')
|
||||
docpath = os.path.abspath(docpath)
|
||||
if os.path.exists(docpath):
|
||||
checker = PintOutputChecker()
|
||||
for name in (name for name in os.listdir(docpath) if name.endswith('.rst')):
|
||||
file = os.path.join(docpath, name)
|
||||
suite.addTest(doctest.DocFileSuite(file,
|
||||
module_relative=False,
|
||||
checker=checker,
|
||||
globs=_GLOBS.get(name, None)))
|
||||
|
||||
|
||||
def test_docs():
|
||||
suite = unittest.TestSuite()
|
||||
add_docs(suite)
|
||||
runner = unittest.TextTestRunner()
|
||||
return runner.run(suite)
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
from __future__ import division, unicode_literals, print_function, absolute_import
|
||||
|
||||
|
||||
import doctest
|
||||
from distutils.version import StrictVersion
|
||||
import re
|
||||
|
||||
from pint.compat import unittest, HAS_NUMPY, HAS_UNCERTAINTIES, NUMPY_VER, PYTHON3
|
||||
|
||||
@@ -41,3 +44,66 @@ def requires_python2():
|
||||
|
||||
def requires_python3():
|
||||
return unittest.skipUnless(PYTHON3, 'Requires Python 3.X.')
|
||||
|
||||
|
||||
_number_re = '([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)'
|
||||
_q_re = re.compile('<Quantity\(' + '\s*' + '(?P<magnitude>%s)' % _number_re +
|
||||
'\s*,\s*' + "'(?P<unit>.*)'" + '\s*' + '\)>')
|
||||
|
||||
_sq_re = re.compile('\s*' + '(?P<magnitude>%s)' % _number_re +
|
||||
'\s' + "(?P<unit>.*)")
|
||||
|
||||
_unit_re = re.compile('<Unit\((.*)\)>')
|
||||
|
||||
|
||||
class PintOutputChecker(doctest.OutputChecker):
|
||||
|
||||
def check_output(self, want, got, optionflags):
|
||||
check = super(PintOutputChecker, self).check_output(want, got, optionflags)
|
||||
if check:
|
||||
return check
|
||||
|
||||
try:
|
||||
if eval(want) == eval(got):
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
for regex in (_q_re, _sq_re):
|
||||
try:
|
||||
parsed_got = regex.match(got.replace(r'\\', '')).groupdict()
|
||||
parsed_want = regex.match(want.replace(r'\\', '')).groupdict()
|
||||
|
||||
v1 = float(parsed_got['magnitude'])
|
||||
v2 = float(parsed_want['magnitude'])
|
||||
|
||||
if abs(v1 - v2) > abs(v1) / 1000:
|
||||
return False
|
||||
|
||||
if parsed_got['unit'] != parsed_want['unit']:
|
||||
return False
|
||||
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
cnt = 0
|
||||
for regex in (_unit_re, ):
|
||||
try:
|
||||
parsed_got, tmp = regex.subn('\1', got)
|
||||
cnt += tmp
|
||||
parsed_want, temp = regex.subn('\1', want)
|
||||
cnt += tmp
|
||||
|
||||
if parsed_got == parsed_want:
|
||||
return True
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
if cnt:
|
||||
# If there was any replacement, we try again the previous methods.
|
||||
return self.check_output(parsed_want, parsed_got, optionflags)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
Reference in New Issue
Block a user