Skip to content

Commit

Permalink
Merge #1235
Browse files Browse the repository at this point in the history
1235: Pytest migration r=hgrecco a=hgrecco

- [x] Closes #947  (insert issue number)
- [x] Executed ``pre-commit run --all-files`` with no errors
- [x] The change is fully covered by automated unit tests
- [x] Documented in docs/ as appropriate
- [x] Added an entry to the CHANGES file


Co-authored-by: Hernan <hernan.grecco@gmail.com>
Co-authored-by: Hernan Grecco <hernan.grecco@gmail.com>
  • Loading branch information
bors[bot] and hgrecco authored Jan 17, 2021
2 parents 012bb5c + 2276040 commit b780f46
Show file tree
Hide file tree
Showing 29 changed files with 3,237 additions and 3,109 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ before_install:
- export TEST_OPTS="-rfsxEX -s --cov=pint --cov-config=.coveragerc"

install:
- conda create -n travis $PKGS pytest pytest-cov coveralls
- conda create -n travis $PKGS pytest pytest-cov coveralls pytest-subtests
- source activate travis
- if [[ $PKGS =~ pandas ]]; then PANDAS=1; else PANDAS=0; fi
- if [[ $PKGS =~ pre-commit ]]; then LINT=1; else LINT=0; fi
Expand Down
58 changes: 51 additions & 7 deletions docs/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,17 @@
Contributing to Pint
====================

You can contribute in different ways:
Pint uses (and thanks):
- github_ to host the code
- travis_ to test all commits and PRs.
- coveralls_ to monitor coverage test coverage
- readthedocs_ to host the documentation.
- `bors-ng`_ as a merge bot and therefore every PR is tested before merging.
- black_, isort_ and flake8_ as code linters and pre-commit_ to enforce them.
- pytest_ to write tests
- sphinx_ to write docs.

You can contribute in different ways:

Report issues
-------------
Expand All @@ -19,18 +28,23 @@ Contribute code
To contribute fixes, code or documentation to Pint, fork Pint in github_ and submit
the changes using a pull request against the **master** branch.

- If you are fixing a bug, add a test to test_issues.py, or amend/enrich the general
test suite to cover the use case.
- If you are submitting new code, add tests and documentation.
- If you are submitting new code, add tests (see below) and documentation.
- Write "Closes #<bug number>" in the PR description or a comment, as described in the
`github docs`_.
- Log the change in the CHANGES file.
- Execute ``black -t py36 . && isort -rc . && flake8`` and resolve any issues.

Pint uses `bors-ng` as a merge bot and therefore every PR is tested before merging.
- Execute ``pre-commit run --all-files`` and resolve any issues.

In any case, feel free to use the `issue tracker`_ to discuss ideas for new features or improvements.

Notice that we will not merge a PR if tests are failing. In certain cases tests pass in your
machine but not in travis. There might be multiple reasons for this but these are some of
the most common

- Your new code does not work for other Python or Numpy versions.
- The documentation is not being built properly or the examples in the docs are
not working.
- linters are reporting that the code does no adhere to the standards.


Setting up your environment
---------------------------
Expand All @@ -44,6 +58,27 @@ environment on Linux or OSX with the following commands::
$ source venv/bin/activate
$ pip install -e .
$ pip install -r requirements_docs.txt
$ pip install pre-commit # This step and the next are optional but recommended.
$ pre-commit install


Writing tests
-------------

We use pytest_ for testing. If you contribute code you need to add tests:

- If you are fixing a bug, add a test to `test_issues.py`, or amend/enrich the general
test suite to cover the use case.
- If you are adding a new feature, add a test in the appropiate place. There is usually
a `test_X.py` for each `X.py` file. There are some other test files that deal with
individual/specific features. If in doubt, ask.
- Prefer functions to classes.
- When using classes, derive from `QuantityTestCase`.
- Use `parametrize` as much as possible.
- Use `fixtures` (see conftest.py) instead of instantiating the registry yourself.
- Checkout `helpers.py` for some convenience functions before reinventing the wheel.
- When your test does not modify the registry, use `sess_registry` fixture.


Running tests and building documentation
----------------------------------------
Expand Down Expand Up @@ -94,3 +129,12 @@ features that work best as an extension pacakage versus direct inclusion in Pint
.. _`issue tracker`: https://github.com/hgrecco/pint/issues
.. _`bors-ng`: https://github.com/bors-ng/bors-ng
.. _`github docs`: https://help.github.com/articles/closing-issues-via-commit-messages/
.. _travis: https://travis-ci.com/
.. _coveralls: https://coveralls.io/
.. _readthedocs: https://readthedocs.org/
.. _pre-commit: https://pre-commit.com/
.. _black: https://black.readthedocs.io/en/stable/
.. _isort: https://pycqa.github.io/isort/
.. _flake8: https://flake8.pycqa.org/en/latest/
.. _pytest: https://docs.pytest.org/en/stable/
.. _sphinx: https://www.sphinx-doc.org/en/master/
133 changes: 9 additions & 124 deletions pint/testsuite/__init__.py
Original file line number Diff line number Diff line change
@@ -1,141 +1,26 @@
import doctest
import logging
import math
import os
import unittest
from contextlib import contextmanager
from logging.handlers import BufferingHandler

from pint import Quantity, UnitRegistry, logger
from pint.compat import ndarray, np
from pint import UnitRegistry
from pint.testsuite.helpers import PintOutputChecker


class TestHandler(BufferingHandler):
def __init__(self, only_warnings=False):
# BufferingHandler takes a "capacity" argument
# so as to know when to flush. As we're overriding
# shouldFlush anyway, we can set a capacity of zero.
# You can call flush() manually to clear out the
# buffer.
self.only_warnings = only_warnings
BufferingHandler.__init__(self, 0)

def shouldFlush(self):
return False

def emit(self, record):
if self.only_warnings and record.level != logging.WARNING:
return
self.buffer.append(record.__dict__)


class BaseTestCase(unittest.TestCase):

CHECK_NO_WARNING = True

@contextmanager
def capture_log(self, level=logging.DEBUG):
th = TestHandler()
th.setLevel(level)
logger.addHandler(th)
if self._test_handler is not None:
buflen = len(self._test_handler.buffer)
yield th.buffer
if self._test_handler is not None:
self._test_handler.buffer = self._test_handler.buffer[:buflen]

def setUp(self):
self._test_handler = None
if self.CHECK_NO_WARNING:
self._test_handler = th = TestHandler()
th.setLevel(logging.WARNING)
logger.addHandler(th)

def tearDown(self):
if self._test_handler is not None:
buf = self._test_handler.buffer
msg = "\n".join(record.get("msg", str(record)) for record in buf)
self.assertEqual(len(buf), 0, msg=f"{len(buf)} warnings raised.\n{msg}")


class QuantityTestCase(BaseTestCase):

FORCE_NDARRAY = False
class QuantityTestCase:
kwargs = {}

@classmethod
def setUpClass(cls):
cls.ureg = UnitRegistry(force_ndarray=cls.FORCE_NDARRAY)
def setup_class(cls):
cls.ureg = UnitRegistry(**cls.kwargs)
cls.Q_ = cls.ureg.Quantity
cls.U_ = cls.ureg.Unit

def _get_comparable_magnitudes(self, first, second, msg):
if isinstance(first, Quantity) and isinstance(second, Quantity):
second = second.to(first)
self.assertEqual(
first.units, second.units, msg=msg + " Units are not equal."
)
m1, m2 = first.magnitude, second.magnitude
elif isinstance(first, Quantity):
self.assertTrue(
first.dimensionless, msg=msg + " The first is not dimensionless."
)
first = first.to("")
m1, m2 = first.magnitude, second
elif isinstance(second, Quantity):
self.assertTrue(
second.dimensionless, msg=msg + " The second is not dimensionless."
)
second = second.to("")
m1, m2 = first, second.magnitude
else:
m1, m2 = first, second

return m1, m2

def assertQuantityEqual(self, first, second, msg=None):
if msg is None:
msg = "Comparing %r and %r. " % (first, second)

m1, m2 = self._get_comparable_magnitudes(first, second, msg)

if isinstance(m1, ndarray) or isinstance(m2, ndarray):
np.testing.assert_array_equal(m1, m2, err_msg=msg)
elif math.isnan(m1):
self.assertTrue(math.isnan(m2), msg)
elif math.isnan(m2):
self.assertTrue(math.isnan(m1), msg)
else:
self.assertEqual(m1, m2, msg)

def assertQuantityAlmostEqual(self, first, second, rtol=1e-07, atol=0, msg=None):
if msg is None:
try:
msg = "Comparing %r and %r. " % (first, second)
except TypeError:
try:
msg = "Comparing %s and %s. " % (first, second)
except Exception:
msg = "Comparing"

m1, m2 = self._get_comparable_magnitudes(first, second, msg)

if isinstance(m1, ndarray) or isinstance(m2, ndarray):
np.testing.assert_allclose(m1, m2, rtol=rtol, atol=atol, err_msg=msg)
elif math.isnan(m1):
self.assertTrue(math.isnan(m2), msg)
elif math.isnan(m2):
self.assertTrue(math.isnan(m1), msg)
else:
self.assertLessEqual(abs(m1 - m2), atol + rtol * abs(m2), msg=msg)


class CaseInsensitveQuantityTestCase(QuantityTestCase):
@classmethod
def setUpClass(cls):
cls.ureg = UnitRegistry(case_sensitive=False)
cls.Q_ = cls.ureg.Quantity
cls.U_ = cls.ureg.Unit
def teardown_class(cls):
cls.ureg = None
cls.Q_ = None
cls.U_ = None


def testsuite():
Expand Down
77 changes: 77 additions & 0 deletions pint/testsuite/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# pytest fixtures

import io

import pytest

import pint


@pytest.fixture
def registry_empty():
return pint.UnitRegistry(None)


@pytest.fixture
def registry_tiny():
return pint.UnitRegistry(
io.StringIO(
"""
yocto- = 1e-24 = y-
zepto- = 1e-21 = z-
atto- = 1e-18 = a-
femto- = 1e-15 = f-
pico- = 1e-12 = p-
nano- = 1e-9 = n-
micro- = 1e-6 = µ- = u-
milli- = 1e-3 = m-
centi- = 1e-2 = c-
deci- = 1e-1 = d-
deca- = 1e+1 = da- = deka-
hecto- = 1e2 = h-
kilo- = 1e3 = k-
mega- = 1e6 = M-
giga- = 1e9 = G-
tera- = 1e12 = T-
peta- = 1e15 = P-
exa- = 1e18 = E-
zetta- = 1e21 = Z-
yotta- = 1e24 = Y-
meter = [length] = m = metre
second = [time] = s = sec
angstrom = 1e-10 * meter = Å = ångström = Å
minute = 60 * second = min
"""
)
)


@pytest.fixture
def func_registry():
return pint.UnitRegistry()


@pytest.fixture(scope="class")
def class_registry():
"""Only use for those test that do not modify the registry."""
return pint.UnitRegistry()


@pytest.fixture(scope="session")
def sess_registry():
"""Only use for those test that do not modify the registry."""
return pint.UnitRegistry()


@pytest.fixture(scope="class")
def class_tiny_app_registry():
ureg_bak = pint.get_application_registry()
ureg = pint.UnitRegistry(None)
ureg.define("foo = []")
ureg.define("bar = foo / 2")
pint.set_application_registry(ureg)
assert pint.get_application_registry() is ureg
yield ureg
pint.set_application_registry(ureg_bak)
Loading

0 comments on commit b780f46

Please sign in to comment.