Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tee should have a public encoding attr #43

Merged
merged 1 commit into from
Feb 19, 2025

Conversation

MatthijsBurgh
Copy link
Contributor

@MatthijsBurgh MatthijsBurgh commented Feb 19, 2025

As also in the original nose library, the Tee object should have a public encoding attribute instead of it being hidden. See https://github.com/nose-devs/nose/blob/7c26ad1e6b7d308cafa328ad34736d34028c122a/nose/plugins/xunit.py#L129

As we use nose in combination with doctests, we get an attribute error when doctest tries to get the encoding of what doctest thinks is just a stdout, but because we use nose, it is a Tee object. Which would be fine, if it had the same attributes.
See https://github.com/python/cpython/blob/v3.8.20/Lib/doctest.py#L1452

@MatthijsBurgh
Copy link
Contributor Author

Please create a new PyPi release after merging this PR.

@mdmintz
Copy link
Owner

mdmintz commented Feb 19, 2025

If there's a bug, I need to see a full example that reproduces the issue with the current state of pynose.
Then I will confirm that your proposed update solves the issue.

@MatthijsBurgh
Copy link
Contributor Author

I don't have a simple example that can be tested easily. You can the CI logs https://dev.azure.com/tue-robotics/tue_robocup/_build/results?buildId=5859&view=logs&j=ce00c4f3-9970-58be-17a6-619446bb3f5a&t=c1c22edd-5a48-59aa-c27e-44463b4e95bc&l=92

Let me know whether this enough for you or not

@MatthijsBurgh
Copy link
Contributor Author

MatthijsBurgh commented Feb 19, 2025

I was able to reproduce it in a simple example.

Using pynose 1.5.3. I have tested in on python 3.8.18 and 3.10.12.

File structure:

├── main.py
└── tests
    ├── __init__.py
    ├── lib
    │   ├── __init__.py
    │   └── factorial.py
    └── test_case.py

main.py

from tests.test_case import _TestDocTests
import unittest

class DocTests(_TestDocTests):
    def __init__(self, method_name="test_doctests"):
        super().__init__(method_name=method_name)


if __name__ == "__main__":
    unittest.main()

tests/test_case.py

import doctest
from pathlib import Path
import importlib
import os
import unittest

class _TestDocTests(unittest.TestCase):
    def __init__(self, method_name: str = "test_doctests"):
        """
        Constructor

        :param method_name: Name of the member variable to run, this should be "test_doctests" and shouldn't
            be changed.
        """
        assert method_name == "test_doctests", "The method_name should be 'test_doctests'. This is the function which" \
                                               "implements the functionality of this TestCase."
        super().__init__(method_name)

    def test_doctests(self):
        """
        Iterates over all Python files in a path and runs doctest.testmod
        """

        path = Path(__file__).parent

        for root, dirs, files in os.walk(path):
            for filename in files:
                if filename.endswith("factorial.py") and "__init__" not in filename:
                    filepath = os.path.join(root, filename)
                    module_name = "tests"
                    module_name = module_name + filepath.split(module_name)[-1]
                    # module_name = module_name[:-3]
                    module_name = module_name.replace("/", ".")
                    module_name = module_name.rstrip(".py")
                    print(f"{module_name=}")
                    mod = importlib.import_module(module_name)
                    print(mod)
                    doctest.testmod(mod, report=1)

        failed_count, attempted_count = doctest.master.summarize(True)
        print("Attempted count: {}".format(attempted_count))
        self.assertEqual(failed_count, 0, "{} out of {} tests failed".format(failed_count, attempted_count))

tests/lib/factorial.py

"""
This is the "example" module.

The example module supplies one function, factorial().  For example,

>>> factorial(5)
120
"""

def factorial(n):
    """Return the factorial of n, an exact integer >= 0.

    >>> [factorial(n) for n in range(6)]
    [1, 1, 2, 6, 24, 120]
    >>> factorial(30)
    265252859812191058636308480000000
    >>> factorial(-1)
    Traceback (most recent call last):
        ...
    ValueError: n must be >= 0

    Factorials of floats are OK, but the float must be an exact integer:
    >>> factorial(30.1)
    Traceback (most recent call last):
        ...
    ValueError: n must be exact integer
    >>> factorial(30.0)
    265252859812191058636308480000000

    It must also not be ridiculously large:
    >>> factorial(1e100)
    Traceback (most recent call last):
        ...
    OverflowError: n too large
    """

    import math
    if not n >= 0:
        raise ValueError("n must be >= 0")
    if math.floor(n) != n:
        raise ValueError("n must be exact integer")
    if n+1 == n:  # catch a value like 1e300
        raise OverflowError("n too large")
    result = 1
    factor = 2
    while factor <= n:
        result *= factor
        factor += 1
    return result


if __name__ == "__main__":
    import doctest
    doctest.testmod()

Run nosetests main.py --with-xunit

It should give you the following error:

module_name='tests.lib.factorial'
<module 'tests.lib.factorial' from '/ROOT_DIR/tests/lib/factorial.py'>
E
======================================================================
ERROR: Iterates over all Python files in a path and runs doctest.testmod
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/ROOT_DIR/tests/test_case.py", line 38, in test_doctests
    doctest.testmod(mod, report=1)
  File "/home/USER/.pyenv/versions/3.8.18/lib/python3.8/doctest.py", line 1956, in testmod
    runner.run(test)
  File "/home/USER/.pyenv/versions/3.8.18/lib/python3.8/doctest.py", line 1452, in run
    encoding = save_stdout.encoding
AttributeError: 'Tee' object has no attribute 'encoding'

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

@mdmintz mdmintz merged commit 1b45425 into mdmintz:master Feb 19, 2025
@mdmintz
Copy link
Owner

mdmintz commented Feb 19, 2025

OK, merged. Will be shipped to PyPI sometime today.

@MatthijsBurgh MatthijsBurgh deleted the patch-1 branch February 19, 2025 17:46
@mdmintz
Copy link
Owner

mdmintz commented Feb 19, 2025

Shipped in 1.5.4 - https://pypi.org/project/pynose/1.5.4/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants