classification
Title: doctest.DocTestSuite error misleading when module has no docstrings
Type: behavior Stage: resolved
Components: Library (Lib) Versions: Python 3.2, Python 3.3, Python 2.7
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: Nosy List: asvetlov, chris.jerdonek, python-dev, r.david.murray
Priority: normal Keywords: easy, patch

Created on 2012-04-23 14:54 by chris.jerdonek, last changed 2012-09-10 16:08 by chris.jerdonek. This issue is now closed.

Files
File name Uploaded Description Edit
issue-14649-1.patch chris.jerdonek, 2012-07-06 12:32 review
issue-14649-2.patch chris.jerdonek, 2012-08-24 23:12 review
issue-14649-3.patch chris.jerdonek, 2012-08-24 23:48 Improving main code comment. review
issue-14649-4.patch chris.jerdonek, 2012-08-25 18:37 review
issue-14649-5.patch chris.jerdonek, 2012-08-28 01:17 review
issue-14649-6.patch chris.jerdonek, 2012-08-29 00:10 review
Messages (27)
msg159022 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-04-23 14:54
When invoking doctest.DocTestSuite's constructor with a module that has no docstrings, doctest raises the following exception:

...
File "/opt/local/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/doctest.py", line 2280, in DocTestSuite
    raise ValueError(module, "has no tests")
ValueError: (<module '[snip]' from '[snip]'>, 'has no tests')

The error message is misleading because the exception is not raised for modules that have docstrings but no doctests, only for modules that have no docstrings.

To be accurate, the message should be something like 'has no docstrings'.
msg164721 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-07-06 12:32
I have attached a patch with tests.
msg168212 - (view) Author: Andrew Svetlov (asvetlov) * (Python committer) Date: 2012-08-14 16:39
I think current behavior is correct.
Every doctest is docstring from tested module, but not every docstring is a valid doctest.
msg168214 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2012-08-14 17:17
Chris isn't suggesting changing the behavior, just the error message.  I agree with his change for exactly the reason you say: currently doctest doesn't complain if a module does have docstrings but none of those docstrings are tests.  So a module could have no tests without the error being raised.

However, I suspect that the current behavior is a bug.  I suspect the intent was to raise an error if there were no *tests*, not if there were no docstrings.  I don't think it is a bug that should be fixed, though.  I have a couple of test runners in various projects that run doctest on *all* modules, the goal being to pick up any doctests that there are or that I may add, even though not all modules have docstrings that contain doctests.  The doctest version of TestCase-based discovery.

So if anything I think this exception should be dropped.  If the report is that no tests are run, that should be enough of an indication that something is wrong, without raising an error, as it has been for the case where there are docstrings but no tests (we do not as far as I know have a bug or feature request that the current behavior is inadequate).
msg168228 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-08-14 19:08
> Every doctest is docstring from tested module, but not every docstring is a valid doctest.

Actually, I'm not sure even this is correct.  doctest will form a unittest *TestSuite* from a module if it has docstrings, but it will not necessarily create a test for each docstring.  You can see this by running code like the following on a target module that contains an empty docstring:

suite = doctest.DocTestSuite('doctest_target')
print(repr(suite))
print(suite.countTestCases())

which outputs--

<unittest.suite.TestSuite tests=[]>
0

So while "tests" (in the doctest code) evaluates to True (because it is a TestSuite instance), it still "has no tests."  If it wants to check for tests, it should probably be evaluating tests.countTestCases(), as David suggested.
msg168232 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-08-14 19:12
> I suspect the intent was to raise an error if there were no *tests*, not if there were no docstrings.

That, or the implementor thought that if no docstrings were found, then that might indicate something went wrong with the parsing.

For background purposes, here is the justification in the code comment:

    elif not tests:
        # Why do we want to do this? Because it reveals a bug that might
        # otherwise be hidden.
        raise ValueError(module, "has no tests")

> I have a couple of test runners in various projects that run doctest on *all* modules

Same here, which is how I noticed the issue.

> So if anything I think this exception should be dropped.  If the report is that no tests are run, that should be enough of an indication that something is wrong

I would be in favor of this. The unittest module's test discovery, for example, does not raise an exception if it finds no tests (to my knowledge).  In what versions would we be able to make this change?

I could prepare another patch.
msg168233 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2012-08-14 19:27
That's a good question.  Perhaps you could argue for it as a bug fix since it doesn't seem to be documented either way...except for the 'exclude_empty' argument of testmod.  If testmod throws an error when there are no docstrings instead of returning a 0 test count or (if exclude_empty is true) skipping the module, then I think we can treat it as a bug and fix it in all active versions.
msg168246 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-08-15 00:23
The problem seems to be restricted only to DocTestSuite.  testmod and DocTestFinder both seem to work fine.  DocTestSuite calls DocTestFinder but not the other way around: nothing calls DocTestSuite.

If in certain versions we do not treat raising the exception as a bug (e.g. in already-released versions), then I am hoping we can at least document the behavior as a known issue in those versions and still change the exception message to be more accurate and helpful.

Aside from ensuring that docstrings are present, another way to work around the issue is to pass a DocTestFinder to DocTestSuite with exclude_empty set to False, as in the following code:

finder = DocTestFinder(exclude_empty=False)
suite = DocTestSuite(module, test_finder=finder)

Depending on what we decide for which versions, I am willing to create two patches: one for the documentation and exception message change (e.g. for stable versions), and another to drop raising the exception (e.g. for future versions).
msg168659 - (view) Author: Andrew Svetlov (asvetlov) * (Python committer) Date: 2012-08-20 13:40
Ok, I'm +0 for that.
msg169097 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-08-24 23:12
Here is an updated patch for review (just for the default branch for now).

The main change from the previous patch is that the ValueError exception is now documented.

A few additional comments/questions:

Is there a better way to signify in the documentation that certain behavior is an unfixable bug as opposed to desired behavior?  Would that be appropriate here?

I created a new folder inside Lib/test for supporting test files specific to doctest.  In addition to the two new files this patch adds, I would also like to move the eight existing support files into this new directory:

doctest_aliases.py
test_doctest.txt
test_doctest3.txt
sample_doctest.py
test_doctest2.py
test_doctest4.txt
test_doctest.py
test_doctest2.txt

Can that be done as a separate change after the patch for this issue, or should it be done before?

Assuming people are okay with keeping this ValueError behavior the same in the maintenance releases (i.e. not handling this as a bug fix), would people be open to changing the behavior in future versions, or should we keep the behavior the same in future versions as well?
msg169104 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2012-08-25 00:54
Personally I prefer to have the test case create the file(s) used in the test dynamically, writing them to the temporary working directory.  Since these are Python modules, you could use the helpers from script_helpers for this.

Otherwise I think your patch is OK.  I don't think we have a doc convention for "unfortunate implementation detail" :(  We have in the past put notes in the doc that say "this may change in the future", though that is usually about desired enhancements.  But perhaps it could apply here as well.
msg169147 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-08-25 17:54
> Personally I prefer to have the test case create the file(s) used in the test dynamically, writing them to the temporary working directory.

I prefer that too, but when I approached this issue, I found that test_doctest doesn't use unittest.  It uses only doctest tests to test itself, and it uses checked-in files to test things like DocTestSuite and DocFileSuite.  For example, test_DocFileSuite has this:

>>> suite = doctest.DocFileSuite('test_doctest.txt',
...                              'test_doctest2.txt',
...                              'test_doctest4.txt')

(Hence the eight supporting files I mentioned in my previous comment, of which these are three.)

Should I break the current way of doing things for this module and introduce unittest and the first dynamically created files?

> We have in the past put notes in the doc that say "this may change in the future", though that is usually about desired enhancements.

That's interesting.  It's like a "Will change in version ..." counterpart to "Changed in version ...."  Come to think of it, that could be a useful practice in general because it would let people future-proof their code more easily and give them incentives to upgrade.

To simplify things for the purposes of this issue, we can discuss adding such wording to earlier versions only if and when we actually change the future behavior.  I can create a separate issue to discuss changing future behavior once this issue is complete.
msg169152 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-08-25 18:37
Small tweak to a code comment in the patch (`tests` is not itself a unittest.TestSuite instance).
msg169244 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-08-28 01:17
Updating the patch after discussing with David on IRC.

The two new files are now added to Lib/test instead of to a subdirectory of Lib/test.  Moving the doctest files to a subdirectory can be discussed and possibly addressed as part of a separate issue.
msg169315 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2012-08-28 22:52
With your patch 5 applied, test_zipimport_support fails.  I took a quick look at this and it looks like it is because we've added a dependency on an external data file to the test for DocTestSuite.

Note that with patch 4 applied, test_pyclbr fails...I found that out by accident.  Not sure why test_zipimport_support didn't fail with that one.  But regardless we'll have to deal with pyclbr if/when we isolate the doctest test files.
msg169317 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-08-28 23:05
> With your patch 5 applied, test_zipimport_support fails.

You're right.  It looks like test_zipimport_support is tightly coupled to test_doctest.  For example--

http://hg.python.org/cpython/file/786d9516663e/Lib/test/test_zipimport_support.py#l94

My apologies for not running the full test suite before uploading those patches.  I'll take a closer look.
msg169323 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-08-29 00:10
I updated the patch to fix the test_zipimport_support tests.  All tests now pass.  The only changes were to Lib/test/test_zipimport_support.py.

I'll make a note about test_pyclbr and test_zipimport_support when I create the issue to move the supporting doctest files into a subdirectory.  Thanks again for the catch, David.
msg170077 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2012-09-09 05:03
OK, 3.2 passes now, but when I merge to 3.3 I get failures.  test_zipimport_support doesn't work on 2.7 either, but there we could just not backport the tests.
msg170078 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-09-09 05:22
I prepared the patch against 3.3 (default), so I wouldn't have expected it to work against 3.2 without further changes.  The behavior in 3.3 after merging is also surprising given that it worked in 3.2 (and for me with direct application).

I will look into it and report back -- providing 3.2 and a "delta" patch, if necessary.  I just wanted to be sure you were okay with the patch for the default branch before doing that extra work.
msg170079 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-09-09 06:12
I tried applying the latest patch (patch #6) to 3.2, merging to default, and then running all tests in default, and the tests seem to all pass for me (in both default and 3.2).  Also, the "diff" for the change in default after that process seems to be identical to the uploaded patch.

Is it possible that you applied the wrong patch at some point in the process?  What test failure were you getting?

Can you try applying the patch as is to the default branch (i.e. without going through 3.2) and see if you still get test failures?
msg170102 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2012-09-09 14:21
Ah, my mistake.  I forgot to do the hg add for the new files.
msg170110 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-09-09 15:37
What workflow/command are you using to apply and then merge the patch?  I didn't need to run "hg add" at any point to go from 3.2 to default, and run the tests, etc.
msg170123 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2012-09-09 18:12
I have separate working directories for 3.2 vs 3.3.  If you have a single working directory, the files would continue to be present even without the add.  The add is definitely needed for a correctly committed changeset.
msg170189 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2012-09-10 14:17
New changeset d012f645b8b2 by R David Murray in branch '3.2':
#14649: clarify DocTestSuite error when there are no docstrings.
http://hg.python.org/cpython/rev/d012f645b8b2

New changeset 6544fc92b528 by R David Murray in branch 'default':
Merge #14649: clarify DocTestSuite error when there are no docstrings.
http://hg.python.org/cpython/rev/6544fc92b528

New changeset b48ef168d8c5 by R David Murray in branch '2.7':
#14649: clarify DocTestSuite error when there are no docstrings.
http://hg.python.org/cpython/rev/b48ef168d8c5
msg170190 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2012-09-10 14:19
Thanks, Chris.
msg170197 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2012-09-10 15:20
New changeset c5fc49bc7a5f by R David Murray in branch '2.7':
#14649: add sample files omitted from previous checkin.
http://hg.python.org/cpython/rev/c5fc49bc7a5f
msg170199 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-09-10 16:08
Thanks a lot, David.
History
Date User Action Args
2012-09-10 16:08:55chris.jerdoneksetmessages: + msg170199
2012-09-10 15:20:57python-devsetmessages: + msg170197
2012-09-10 14:19:39r.david.murraysetstatus: open -> closed
resolution: fixed
messages: + msg170190

stage: patch review -> resolved
2012-09-10 14:17:32python-devsetnosy: + python-dev
messages: + msg170189
2012-09-09 18:12:05r.david.murraysetmessages: + msg170123
2012-09-09 15:37:08chris.jerdoneksetmessages: + msg170110
2012-09-09 14:21:24r.david.murraysetmessages: + msg170102
2012-09-09 06:12:32chris.jerdoneksetmessages: + msg170079
2012-09-09 05:22:03chris.jerdoneksetmessages: + msg170078
2012-09-09 05:03:03r.david.murraysetmessages: + msg170077
2012-08-29 00:10:16chris.jerdoneksetfiles: + issue-14649-6.patch

messages: + msg169323
2012-08-28 23:05:22chris.jerdoneksetmessages: + msg169317
2012-08-28 22:52:33r.david.murraysetmessages: + msg169315
2012-08-28 01:17:32chris.jerdoneksetfiles: + issue-14649-5.patch

messages: + msg169244
2012-08-25 18:37:31chris.jerdoneksetfiles: + issue-14649-4.patch

messages: + msg169152
2012-08-25 17:54:54chris.jerdoneksetmessages: + msg169147
2012-08-25 00:54:58r.david.murraysetmessages: + msg169104
2012-08-24 23:48:56chris.jerdoneksetfiles: + issue-14649-3.patch
2012-08-24 23:12:15chris.jerdoneksetfiles: + issue-14649-2.patch

messages: + msg169097
2012-08-20 13:40:49asvetlovsetmessages: + msg168659
2012-08-15 00:23:02chris.jerdoneksetmessages: + msg168246
2012-08-14 19:27:34r.david.murraysetmessages: + msg168233
2012-08-14 19:12:56chris.jerdoneksetmessages: + msg168232
2012-08-14 19:08:57chris.jerdoneksetmessages: + msg168228
2012-08-14 17:17:44r.david.murraysetmessages: + msg168214
2012-08-14 16:39:04asvetlovsetmessages: + msg168212
2012-08-13 20:35:03chris.jerdoneksetnosy: + asvetlov
2012-07-06 12:52:51chris.jerdoneksetnosy: + r.david.murray
2012-07-06 12:32:47chris.jerdoneksetkeywords: + easy, patch
files: + issue-14649-1.patch
messages: + msg164721

stage: patch review
2012-07-06 12:31:18chris.jerdoneksetversions: + Python 3.3
2012-04-23 14:54:21chris.jerdonekcreate