1823b8294SYaroslav Brustinov"""Use the Doctest plugin with ``--with-doctest`` or the NOSE_WITH_DOCTEST
2823b8294SYaroslav Brustinovenvironment variable to enable collection and execution of :mod:`doctests
3823b8294SYaroslav Brustinov<doctest>`.  Because doctests are usually included in the tested package
4823b8294SYaroslav Brustinov(instead of being grouped into packages or modules of their own), nose only
5823b8294SYaroslav Brustinovlooks for them in the non-test packages it discovers in the working directory.
6823b8294SYaroslav Brustinov
7823b8294SYaroslav BrustinovDoctests may also be placed into files other than python modules, in which
8823b8294SYaroslav Brustinovcase they can be collected and executed by using the ``--doctest-extension``
9823b8294SYaroslav Brustinovswitch or NOSE_DOCTEST_EXTENSION environment variable to indicate which file
10823b8294SYaroslav Brustinovextension(s) to load.
11823b8294SYaroslav Brustinov
12823b8294SYaroslav BrustinovWhen loading doctests from non-module files, use the ``--doctest-fixtures``
13823b8294SYaroslav Brustinovswitch to specify how to find modules containing fixtures for the tests. A
14823b8294SYaroslav Brustinovmodule name will be produced by appending the value of that switch to the base
15823b8294SYaroslav Brustinovname of each doctest file loaded. For example, a doctest file "widgets.rst"
16823b8294SYaroslav Brustinovwith the switch ``--doctest_fixtures=_fixt`` will load fixtures from the module
17823b8294SYaroslav Brustinov``widgets_fixt.py``.
18823b8294SYaroslav Brustinov
19823b8294SYaroslav BrustinovA fixtures module may define any or all of the following functions:
20823b8294SYaroslav Brustinov
21823b8294SYaroslav Brustinov* setup([module]) or setup_module([module])
22823b8294SYaroslav Brustinov
23823b8294SYaroslav Brustinov  Called before the test runs. You may raise SkipTest to skip all tests.
24823b8294SYaroslav Brustinov
25823b8294SYaroslav Brustinov* teardown([module]) or teardown_module([module])
26823b8294SYaroslav Brustinov
27823b8294SYaroslav Brustinov  Called after the test runs, if setup/setup_module did not raise an
28823b8294SYaroslav Brustinov  unhandled exception.
29823b8294SYaroslav Brustinov
30823b8294SYaroslav Brustinov* setup_test(test)
31823b8294SYaroslav Brustinov
32823b8294SYaroslav Brustinov  Called before the test. NOTE: the argument passed is a
33823b8294SYaroslav Brustinov  doctest.DocTest instance, *not* a unittest.TestCase.
34823b8294SYaroslav Brustinov
35823b8294SYaroslav Brustinov* teardown_test(test)
36823b8294SYaroslav Brustinov
37823b8294SYaroslav Brustinov  Called after the test, if setup_test did not raise an exception. NOTE: the
38823b8294SYaroslav Brustinov  argument passed is a doctest.DocTest instance, *not* a unittest.TestCase.
39823b8294SYaroslav Brustinov
40823b8294SYaroslav BrustinovDoctests are run like any other test, with the exception that output
41823b8294SYaroslav Brustinovcapture does not work; doctest does its own output capture while running a
42823b8294SYaroslav Brustinovtest.
43823b8294SYaroslav Brustinov
44823b8294SYaroslav Brustinov.. note ::
45823b8294SYaroslav Brustinov
46823b8294SYaroslav Brustinov   See :doc:`../doc_tests/test_doctest_fixtures/doctest_fixtures` for
47823b8294SYaroslav Brustinov   additional documentation and examples.
48823b8294SYaroslav Brustinov
49823b8294SYaroslav Brustinov"""
50823b8294SYaroslav Brustinovfrom __future__ import generators
51823b8294SYaroslav Brustinov
52823b8294SYaroslav Brustinovimport logging
53823b8294SYaroslav Brustinovimport os
54823b8294SYaroslav Brustinovimport sys
55823b8294SYaroslav Brustinovimport unittest
56823b8294SYaroslav Brustinovfrom inspect import getmodule
57823b8294SYaroslav Brustinovfrom nose.plugins.base import Plugin
58823b8294SYaroslav Brustinovfrom nose.suite import ContextList
59823b8294SYaroslav Brustinovfrom nose.util import anyp, getpackage, test_address, resolve_name, \
60823b8294SYaroslav Brustinov     src, tolist, isproperty
61823b8294SYaroslav Brustinovtry:
62823b8294SYaroslav Brustinov    from cStringIO import StringIO
63823b8294SYaroslav Brustinovexcept ImportError:
64823b8294SYaroslav Brustinov    from StringIO import StringIO
65823b8294SYaroslav Brustinovimport sys
66823b8294SYaroslav Brustinovimport __builtin__ as builtin_mod
67823b8294SYaroslav Brustinov
68823b8294SYaroslav Brustinovlog = logging.getLogger(__name__)
69823b8294SYaroslav Brustinov
70823b8294SYaroslav Brustinovtry:
71823b8294SYaroslav Brustinov    import doctest
72823b8294SYaroslav Brustinov    doctest.DocTestCase
73823b8294SYaroslav Brustinov    # system version of doctest is acceptable, but needs a monkeypatch
74823b8294SYaroslav Brustinovexcept (ImportError, AttributeError):
75823b8294SYaroslav Brustinov    # system version is too old
76823b8294SYaroslav Brustinov    import nose.ext.dtcompat as doctest
77823b8294SYaroslav Brustinov
78823b8294SYaroslav Brustinov
79823b8294SYaroslav Brustinov#
80823b8294SYaroslav Brustinov# Doctest and coverage don't get along, so we need to create
81823b8294SYaroslav Brustinov# a monkeypatch that will replace the part of doctest that
82823b8294SYaroslav Brustinov# interferes with coverage reports.
83823b8294SYaroslav Brustinov#
84823b8294SYaroslav Brustinov# The monkeypatch is based on this zope patch:
85823b8294SYaroslav Brustinov# http://svn.zope.org/Zope3/trunk/src/zope/testing/doctest.py?rev=28679&r1=28703&r2=28705
86823b8294SYaroslav Brustinov#
87823b8294SYaroslav Brustinov_orp = doctest._OutputRedirectingPdb
88823b8294SYaroslav Brustinov
89823b8294SYaroslav Brustinovclass NoseOutputRedirectingPdb(_orp):
90823b8294SYaroslav Brustinov    def __init__(self, out):
91823b8294SYaroslav Brustinov        self.__debugger_used = False
92823b8294SYaroslav Brustinov        _orp.__init__(self, out)
93823b8294SYaroslav Brustinov
94823b8294SYaroslav Brustinov    def set_trace(self):
95823b8294SYaroslav Brustinov        self.__debugger_used = True
96823b8294SYaroslav Brustinov        _orp.set_trace(self, sys._getframe().f_back)
97823b8294SYaroslav Brustinov
98823b8294SYaroslav Brustinov    def set_continue(self):
99823b8294SYaroslav Brustinov        # Calling set_continue unconditionally would break unit test
100823b8294SYaroslav Brustinov        # coverage reporting, as Bdb.set_continue calls sys.settrace(None).
101823b8294SYaroslav Brustinov        if self.__debugger_used:
102823b8294SYaroslav Brustinov            _orp.set_continue(self)
103823b8294SYaroslav Brustinovdoctest._OutputRedirectingPdb = NoseOutputRedirectingPdb
104823b8294SYaroslav Brustinov
105823b8294SYaroslav Brustinov
106823b8294SYaroslav Brustinovclass DoctestSuite(unittest.TestSuite):
107823b8294SYaroslav Brustinov    """
108823b8294SYaroslav Brustinov    Doctest suites are parallelizable at the module or file level only,
109823b8294SYaroslav Brustinov    since they may be attached to objects that are not individually
110823b8294SYaroslav Brustinov    addressable (like properties). This suite subclass is used when
111823b8294SYaroslav Brustinov    loading doctests from a module to ensure that behavior.
112823b8294SYaroslav Brustinov
113823b8294SYaroslav Brustinov    This class is used only if the plugin is not fully prepared;
114823b8294SYaroslav Brustinov    in normal use, the loader's suiteClass is used.
115823b8294SYaroslav Brustinov
116823b8294SYaroslav Brustinov    """
117823b8294SYaroslav Brustinov    can_split = False
118823b8294SYaroslav Brustinov
119823b8294SYaroslav Brustinov    def __init__(self, tests=(), context=None, can_split=False):
120823b8294SYaroslav Brustinov        self.context = context
121823b8294SYaroslav Brustinov        self.can_split = can_split
122823b8294SYaroslav Brustinov        unittest.TestSuite.__init__(self, tests=tests)
123823b8294SYaroslav Brustinov
124823b8294SYaroslav Brustinov    def address(self):
125823b8294SYaroslav Brustinov        return test_address(self.context)
126823b8294SYaroslav Brustinov
127823b8294SYaroslav Brustinov    def __iter__(self):
128823b8294SYaroslav Brustinov        # 2.3 compat
129823b8294SYaroslav Brustinov        return iter(self._tests)
130823b8294SYaroslav Brustinov
131823b8294SYaroslav Brustinov    def __str__(self):
132823b8294SYaroslav Brustinov        return str(self._tests)
133823b8294SYaroslav Brustinov
134823b8294SYaroslav Brustinov
135823b8294SYaroslav Brustinovclass Doctest(Plugin):
136823b8294SYaroslav Brustinov    """
137823b8294SYaroslav Brustinov    Activate doctest plugin to find and run doctests in non-test modules.
138823b8294SYaroslav Brustinov    """
139823b8294SYaroslav Brustinov    extension = None
140823b8294SYaroslav Brustinov    suiteClass = DoctestSuite
141823b8294SYaroslav Brustinov
142823b8294SYaroslav Brustinov    def options(self, parser, env):
143823b8294SYaroslav Brustinov        """Register commmandline options.
144823b8294SYaroslav Brustinov        """
145823b8294SYaroslav Brustinov        Plugin.options(self, parser, env)
146823b8294SYaroslav Brustinov        parser.add_option('--doctest-tests', action='store_true',
147823b8294SYaroslav Brustinov                          dest='doctest_tests',
148823b8294SYaroslav Brustinov                          default=env.get('NOSE_DOCTEST_TESTS'),
149823b8294SYaroslav Brustinov                          help="Also look for doctests in test modules. "
150823b8294SYaroslav Brustinov                          "Note that classes, methods and functions should "
151823b8294SYaroslav Brustinov                          "have either doctests or non-doctest tests, "
152823b8294SYaroslav Brustinov                          "not both. [NOSE_DOCTEST_TESTS]")
153823b8294SYaroslav Brustinov        parser.add_option('--doctest-extension', action="append",
154823b8294SYaroslav Brustinov                          dest="doctestExtension",
155823b8294SYaroslav Brustinov                          metavar="EXT",
156823b8294SYaroslav Brustinov                          help="Also look for doctests in files with "
157823b8294SYaroslav Brustinov                          "this extension [NOSE_DOCTEST_EXTENSION]")
158823b8294SYaroslav Brustinov        parser.add_option('--doctest-result-variable',
159823b8294SYaroslav Brustinov                          dest='doctest_result_var',
160823b8294SYaroslav Brustinov                          default=env.get('NOSE_DOCTEST_RESULT_VAR'),
161823b8294SYaroslav Brustinov                          metavar="VAR",
162823b8294SYaroslav Brustinov                          help="Change the variable name set to the result of "
163823b8294SYaroslav Brustinov                          "the last interpreter command from the default '_'. "
164823b8294SYaroslav Brustinov                          "Can be used to avoid conflicts with the _() "
165823b8294SYaroslav Brustinov                          "function used for text translation. "
166823b8294SYaroslav Brustinov                          "[NOSE_DOCTEST_RESULT_VAR]")
167823b8294SYaroslav Brustinov        parser.add_option('--doctest-fixtures', action="store",
168823b8294SYaroslav Brustinov                          dest="doctestFixtures",
169823b8294SYaroslav Brustinov                          metavar="SUFFIX",
170823b8294SYaroslav Brustinov                          help="Find fixtures for a doctest file in module "
171823b8294SYaroslav Brustinov                          "with this name appended to the base name "
172823b8294SYaroslav Brustinov                          "of the doctest file")
173823b8294SYaroslav Brustinov        parser.add_option('--doctest-options', action="append",
174823b8294SYaroslav Brustinov                          dest="doctestOptions",
175823b8294SYaroslav Brustinov                          metavar="OPTIONS",
176823b8294SYaroslav Brustinov                          help="Specify options to pass to doctest. " +
177823b8294SYaroslav Brustinov                          "Eg. '+ELLIPSIS,+NORMALIZE_WHITESPACE'")
178823b8294SYaroslav Brustinov        # Set the default as a list, if given in env; otherwise
179823b8294SYaroslav Brustinov        # an additional value set on the command line will cause
180823b8294SYaroslav Brustinov        # an error.
181823b8294SYaroslav Brustinov        env_setting = env.get('NOSE_DOCTEST_EXTENSION')
182823b8294SYaroslav Brustinov        if env_setting is not None:
183823b8294SYaroslav Brustinov            parser.set_defaults(doctestExtension=tolist(env_setting))
184823b8294SYaroslav Brustinov
185823b8294SYaroslav Brustinov    def configure(self, options, config):
186823b8294SYaroslav Brustinov        """Configure plugin.
187823b8294SYaroslav Brustinov        """
188823b8294SYaroslav Brustinov        Plugin.configure(self, options, config)
189823b8294SYaroslav Brustinov        self.doctest_result_var = options.doctest_result_var
190823b8294SYaroslav Brustinov        self.doctest_tests = options.doctest_tests
191823b8294SYaroslav Brustinov        self.extension = tolist(options.doctestExtension)
192823b8294SYaroslav Brustinov        self.fixtures = options.doctestFixtures
193823b8294SYaroslav Brustinov        self.finder = doctest.DocTestFinder()
194823b8294SYaroslav Brustinov        self.optionflags = 0
195823b8294SYaroslav Brustinov        if options.doctestOptions:
196823b8294SYaroslav Brustinov            flags = ",".join(options.doctestOptions).split(',')
197823b8294SYaroslav Brustinov            for flag in flags:
198823b8294SYaroslav Brustinov                if not flag or flag[0] not in '+-':
199823b8294SYaroslav Brustinov                    raise ValueError(
200823b8294SYaroslav Brustinov                        "Must specify doctest options with starting " +
201823b8294SYaroslav Brustinov                        "'+' or '-'.  Got %s" % (flag,))
202823b8294SYaroslav Brustinov                mode, option_name = flag[0], flag[1:]
203823b8294SYaroslav Brustinov                option_flag = doctest.OPTIONFLAGS_BY_NAME.get(option_name)
204823b8294SYaroslav Brustinov                if not option_flag:
205823b8294SYaroslav Brustinov                    raise ValueError("Unknown doctest option %s" %
206823b8294SYaroslav Brustinov                                     (option_name,))
207823b8294SYaroslav Brustinov                if mode == '+':
208823b8294SYaroslav Brustinov                    self.optionflags |= option_flag
209823b8294SYaroslav Brustinov                elif mode == '-':
210823b8294SYaroslav Brustinov                    self.optionflags &= ~option_flag
211823b8294SYaroslav Brustinov
212823b8294SYaroslav Brustinov    def prepareTestLoader(self, loader):
213823b8294SYaroslav Brustinov        """Capture loader's suiteClass.
214823b8294SYaroslav Brustinov
215823b8294SYaroslav Brustinov        This is used to create test suites from doctest files.
216823b8294SYaroslav Brustinov
217823b8294SYaroslav Brustinov        """
218823b8294SYaroslav Brustinov        self.suiteClass = loader.suiteClass
219823b8294SYaroslav Brustinov
220823b8294SYaroslav Brustinov    def loadTestsFromModule(self, module):
221823b8294SYaroslav Brustinov        """Load doctests from the module.
222823b8294SYaroslav Brustinov        """
223823b8294SYaroslav Brustinov        log.debug("loading from %s", module)
224823b8294SYaroslav Brustinov        if not self.matches(module.__name__):
225823b8294SYaroslav Brustinov            log.debug("Doctest doesn't want module %s", module)
226823b8294SYaroslav Brustinov            return
227823b8294SYaroslav Brustinov        try:
228823b8294SYaroslav Brustinov            tests = self.finder.find(module)
229823b8294SYaroslav Brustinov        except AttributeError:
230823b8294SYaroslav Brustinov            log.exception("Attribute error loading from %s", module)
231823b8294SYaroslav Brustinov            # nose allows module.__test__ = False; doctest does not and throws
232823b8294SYaroslav Brustinov            # AttributeError
233823b8294SYaroslav Brustinov            return
234823b8294SYaroslav Brustinov        if not tests:
235823b8294SYaroslav Brustinov            log.debug("No tests found in %s", module)
236823b8294SYaroslav Brustinov            return
237823b8294SYaroslav Brustinov        tests.sort()
238823b8294SYaroslav Brustinov        module_file = src(module.__file__)
239823b8294SYaroslav Brustinov        # FIXME this breaks the id plugin somehow (tests probably don't
240823b8294SYaroslav Brustinov        # get wrapped in result proxy or something)
241823b8294SYaroslav Brustinov        cases = []
242823b8294SYaroslav Brustinov        for test in tests:
243823b8294SYaroslav Brustinov            if not test.examples:
244823b8294SYaroslav Brustinov                continue
245823b8294SYaroslav Brustinov            if not test.filename:
246823b8294SYaroslav Brustinov                test.filename = module_file
247823b8294SYaroslav Brustinov            cases.append(DocTestCase(test,
248823b8294SYaroslav Brustinov                                     optionflags=self.optionflags,
249823b8294SYaroslav Brustinov                                     result_var=self.doctest_result_var))
250823b8294SYaroslav Brustinov        if cases:
251823b8294SYaroslav Brustinov            yield self.suiteClass(cases, context=module, can_split=False)
252823b8294SYaroslav Brustinov
253823b8294SYaroslav Brustinov    def loadTestsFromFile(self, filename):
254823b8294SYaroslav Brustinov        """Load doctests from the file.
255823b8294SYaroslav Brustinov
256823b8294SYaroslav Brustinov        Tests are loaded only if filename's extension matches
257823b8294SYaroslav Brustinov        configured doctest extension.
258823b8294SYaroslav Brustinov
259823b8294SYaroslav Brustinov        """
260823b8294SYaroslav Brustinov        if self.extension and anyp(filename.endswith, self.extension):
261823b8294SYaroslav Brustinov            name = os.path.basename(filename)
262823b8294SYaroslav Brustinov            dh = open(filename)
263823b8294SYaroslav Brustinov            try:
264823b8294SYaroslav Brustinov                doc = dh.read()
265823b8294SYaroslav Brustinov            finally:
266823b8294SYaroslav Brustinov                dh.close()
267823b8294SYaroslav Brustinov
268823b8294SYaroslav Brustinov            fixture_context = None
269823b8294SYaroslav Brustinov            globs = {'__file__': filename}
270823b8294SYaroslav Brustinov            if self.fixtures:
271823b8294SYaroslav Brustinov                base, ext = os.path.splitext(name)
272823b8294SYaroslav Brustinov                dirname = os.path.dirname(filename)
273823b8294SYaroslav Brustinov                sys.path.append(dirname)
274823b8294SYaroslav Brustinov                fixt_mod = base + self.fixtures
275823b8294SYaroslav Brustinov                try:
276823b8294SYaroslav Brustinov                    fixture_context = __import__(
277823b8294SYaroslav Brustinov                        fixt_mod, globals(), locals(), ["nop"])
278823b8294SYaroslav Brustinov                except ImportError, e:
279823b8294SYaroslav Brustinov                    log.debug(
280823b8294SYaroslav Brustinov                        "Could not import %s: %s (%s)", fixt_mod, e, sys.path)
281823b8294SYaroslav Brustinov                log.debug("Fixture module %s resolved to %s",
282823b8294SYaroslav Brustinov                          fixt_mod, fixture_context)
283823b8294SYaroslav Brustinov                if hasattr(fixture_context, 'globs'):
284823b8294SYaroslav Brustinov                    globs = fixture_context.globs(globs)
285823b8294SYaroslav Brustinov            parser = doctest.DocTestParser()
286823b8294SYaroslav Brustinov            test = parser.get_doctest(
287823b8294SYaroslav Brustinov                doc, globs=globs, name=name,
288823b8294SYaroslav Brustinov                filename=filename, lineno=0)
289823b8294SYaroslav Brustinov            if test.examples:
290823b8294SYaroslav Brustinov                case = DocFileCase(
291823b8294SYaroslav Brustinov                    test,
292823b8294SYaroslav Brustinov                    optionflags=self.optionflags,
293823b8294SYaroslav Brustinov                    setUp=getattr(fixture_context, 'setup_test', None),
294823b8294SYaroslav Brustinov                    tearDown=getattr(fixture_context, 'teardown_test', None),
295823b8294SYaroslav Brustinov                    result_var=self.doctest_result_var)
296823b8294SYaroslav Brustinov                if fixture_context:
297823b8294SYaroslav Brustinov                    yield ContextList((case,), context=fixture_context)
298823b8294SYaroslav Brustinov                else:
299823b8294SYaroslav Brustinov                    yield case
300823b8294SYaroslav Brustinov            else:
301823b8294SYaroslav Brustinov                yield False # no tests to load
302823b8294SYaroslav Brustinov
303823b8294SYaroslav Brustinov    def makeTest(self, obj, parent):
304823b8294SYaroslav Brustinov        """Look for doctests in the given object, which will be a
305823b8294SYaroslav Brustinov        function, method or class.
306823b8294SYaroslav Brustinov        """
307823b8294SYaroslav Brustinov        name = getattr(obj, '__name__', 'Unnammed %s' % type(obj))
308823b8294SYaroslav Brustinov        doctests = self.finder.find(obj, module=getmodule(parent), name=name)
309823b8294SYaroslav Brustinov        if doctests:
310823b8294SYaroslav Brustinov            for test in doctests:
311823b8294SYaroslav Brustinov                if len(test.examples) == 0:
312823b8294SYaroslav Brustinov                    continue
313823b8294SYaroslav Brustinov                yield DocTestCase(test, obj=obj, optionflags=self.optionflags,
314823b8294SYaroslav Brustinov                                  result_var=self.doctest_result_var)
315823b8294SYaroslav Brustinov
316823b8294SYaroslav Brustinov    def matches(self, name):
317823b8294SYaroslav Brustinov        # FIXME this seems wrong -- nothing is ever going to
318823b8294SYaroslav Brustinov        # fail this test, since we're given a module NAME not FILE
319823b8294SYaroslav Brustinov        if name == '__init__.py':
320823b8294SYaroslav Brustinov            return False
321823b8294SYaroslav Brustinov        # FIXME don't think we need include/exclude checks here?
322823b8294SYaroslav Brustinov        return ((self.doctest_tests or not self.conf.testMatch.search(name)
323823b8294SYaroslav Brustinov                 or (self.conf.include
324823b8294SYaroslav Brustinov                     and filter(None,
325823b8294SYaroslav Brustinov                                [inc.search(name)
326823b8294SYaroslav Brustinov                                 for inc in self.conf.include])))
327823b8294SYaroslav Brustinov                and (not self.conf.exclude
328823b8294SYaroslav Brustinov                     or not filter(None,
329823b8294SYaroslav Brustinov                                   [exc.search(name)
330823b8294SYaroslav Brustinov                                    for exc in self.conf.exclude])))
331823b8294SYaroslav Brustinov
332823b8294SYaroslav Brustinov    def wantFile(self, file):
333823b8294SYaroslav Brustinov        """Override to select all modules and any file ending with
334823b8294SYaroslav Brustinov        configured doctest extension.
335823b8294SYaroslav Brustinov        """
336823b8294SYaroslav Brustinov        # always want .py files
337823b8294SYaroslav Brustinov        if file.endswith('.py'):
338823b8294SYaroslav Brustinov            return True
339823b8294SYaroslav Brustinov        # also want files that match my extension
340823b8294SYaroslav Brustinov        if (self.extension
341823b8294SYaroslav Brustinov            and anyp(file.endswith, self.extension)
342823b8294SYaroslav Brustinov            and (not self.conf.exclude
343823b8294SYaroslav Brustinov                 or not filter(None,
344823b8294SYaroslav Brustinov                               [exc.search(file)
345823b8294SYaroslav Brustinov                                for exc in self.conf.exclude]))):
346823b8294SYaroslav Brustinov            return True
347823b8294SYaroslav Brustinov        return None
348823b8294SYaroslav Brustinov
349823b8294SYaroslav Brustinov
350823b8294SYaroslav Brustinovclass DocTestCase(doctest.DocTestCase):
351823b8294SYaroslav Brustinov    """Overrides DocTestCase to
352823b8294SYaroslav Brustinov    provide an address() method that returns the correct address for
353823b8294SYaroslav Brustinov    the doctest case. To provide hints for address(), an obj may also
354823b8294SYaroslav Brustinov    be passed -- this will be used as the test object for purposes of
355823b8294SYaroslav Brustinov    determining the test address, if it is provided.
356823b8294SYaroslav Brustinov    """
357823b8294SYaroslav Brustinov    def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
358823b8294SYaroslav Brustinov                 checker=None, obj=None, result_var='_'):
359823b8294SYaroslav Brustinov        self._result_var = result_var
360823b8294SYaroslav Brustinov        self._nose_obj = obj
361823b8294SYaroslav Brustinov        super(DocTestCase, self).__init__(
362823b8294SYaroslav Brustinov            test, optionflags=optionflags, setUp=setUp, tearDown=tearDown,
363823b8294SYaroslav Brustinov            checker=checker)
364823b8294SYaroslav Brustinov
365823b8294SYaroslav Brustinov    def address(self):
366823b8294SYaroslav Brustinov        if self._nose_obj is not None:
367823b8294SYaroslav Brustinov            return test_address(self._nose_obj)
368823b8294SYaroslav Brustinov        obj = resolve_name(self._dt_test.name)
369823b8294SYaroslav Brustinov
370823b8294SYaroslav Brustinov        if isproperty(obj):
371823b8294SYaroslav Brustinov            # properties have no connection to the class they are in
372823b8294SYaroslav Brustinov            # so we can't just look 'em up, we have to first look up
373823b8294SYaroslav Brustinov            # the class, then stick the prop on the end
374823b8294SYaroslav Brustinov            parts = self._dt_test.name.split('.')
375823b8294SYaroslav Brustinov            class_name = '.'.join(parts[:-1])
376823b8294SYaroslav Brustinov            cls = resolve_name(class_name)
377823b8294SYaroslav Brustinov            base_addr = test_address(cls)
378823b8294SYaroslav Brustinov            return (base_addr[0], base_addr[1],
379823b8294SYaroslav Brustinov                    '.'.join([base_addr[2], parts[-1]]))
380823b8294SYaroslav Brustinov        else:
381823b8294SYaroslav Brustinov            return test_address(obj)
382823b8294SYaroslav Brustinov
383823b8294SYaroslav Brustinov    # doctests loaded via find(obj) omit the module name
384823b8294SYaroslav Brustinov    # so we need to override id, __repr__ and shortDescription
385823b8294SYaroslav Brustinov    # bonus: this will squash a 2.3 vs 2.4 incompatiblity
386823b8294SYaroslav Brustinov    def id(self):
387823b8294SYaroslav Brustinov        name = self._dt_test.name
388823b8294SYaroslav Brustinov        filename = self._dt_test.filename
389823b8294SYaroslav Brustinov        if filename is not None:
390823b8294SYaroslav Brustinov            pk = getpackage(filename)
391823b8294SYaroslav Brustinov            if pk is None:
392823b8294SYaroslav Brustinov                return name
393823b8294SYaroslav Brustinov            if not name.startswith(pk):
394823b8294SYaroslav Brustinov                name = "%s.%s" % (pk, name)
395823b8294SYaroslav Brustinov        return name
396823b8294SYaroslav Brustinov
397823b8294SYaroslav Brustinov    def __repr__(self):
398823b8294SYaroslav Brustinov        name = self.id()
399823b8294SYaroslav Brustinov        name = name.split('.')
400823b8294SYaroslav Brustinov        return "%s (%s)" % (name[-1], '.'.join(name[:-1]))
401823b8294SYaroslav Brustinov    __str__ = __repr__
402823b8294SYaroslav Brustinov
403823b8294SYaroslav Brustinov    def shortDescription(self):
404823b8294SYaroslav Brustinov        return 'Doctest: %s' % self.id()
405823b8294SYaroslav Brustinov
406823b8294SYaroslav Brustinov    def setUp(self):
407823b8294SYaroslav Brustinov        if self._result_var is not None:
408823b8294SYaroslav Brustinov            self._old_displayhook = sys.displayhook
409823b8294SYaroslav Brustinov            sys.displayhook = self._displayhook
410823b8294SYaroslav Brustinov        super(DocTestCase, self).setUp()
411823b8294SYaroslav Brustinov
412823b8294SYaroslav Brustinov    def _displayhook(self, value):
413823b8294SYaroslav Brustinov        if value is None:
414823b8294SYaroslav Brustinov            return
415823b8294SYaroslav Brustinov        setattr(builtin_mod, self._result_var,  value)
416823b8294SYaroslav Brustinov        print repr(value)
417823b8294SYaroslav Brustinov
418823b8294SYaroslav Brustinov    def tearDown(self):
419823b8294SYaroslav Brustinov        super(DocTestCase, self).tearDown()
420823b8294SYaroslav Brustinov        if self._result_var is not None:
421823b8294SYaroslav Brustinov            sys.displayhook = self._old_displayhook
422823b8294SYaroslav Brustinov            delattr(builtin_mod, self._result_var)
423823b8294SYaroslav Brustinov
424823b8294SYaroslav Brustinov
425823b8294SYaroslav Brustinovclass DocFileCase(doctest.DocFileCase):
426823b8294SYaroslav Brustinov    """Overrides to provide address() method that returns the correct
427823b8294SYaroslav Brustinov    address for the doc file case.
428823b8294SYaroslav Brustinov    """
429823b8294SYaroslav Brustinov    def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
430823b8294SYaroslav Brustinov                 checker=None, result_var='_'):
431823b8294SYaroslav Brustinov        self._result_var = result_var
432823b8294SYaroslav Brustinov        super(DocFileCase, self).__init__(
433823b8294SYaroslav Brustinov            test, optionflags=optionflags, setUp=setUp, tearDown=tearDown,
434823b8294SYaroslav Brustinov            checker=None)
435823b8294SYaroslav Brustinov
436823b8294SYaroslav Brustinov    def address(self):
437823b8294SYaroslav Brustinov        return (self._dt_test.filename, None, None)
438823b8294SYaroslav Brustinov
439823b8294SYaroslav Brustinov    def setUp(self):
440823b8294SYaroslav Brustinov        if self._result_var is not None:
441823b8294SYaroslav Brustinov            self._old_displayhook = sys.displayhook
442823b8294SYaroslav Brustinov            sys.displayhook = self._displayhook
443823b8294SYaroslav Brustinov        super(DocFileCase, self).setUp()
444823b8294SYaroslav Brustinov
445823b8294SYaroslav Brustinov    def _displayhook(self, value):
446823b8294SYaroslav Brustinov        if value is None:
447823b8294SYaroslav Brustinov            return
448823b8294SYaroslav Brustinov        setattr(builtin_mod, self._result_var, value)
449823b8294SYaroslav Brustinov        print repr(value)
450823b8294SYaroslav Brustinov
451823b8294SYaroslav Brustinov    def tearDown(self):
452823b8294SYaroslav Brustinov        super(DocFileCase, self).tearDown()
453823b8294SYaroslav Brustinov        if self._result_var is not None:
454823b8294SYaroslav Brustinov            sys.displayhook = self._old_displayhook
455823b8294SYaroslav Brustinov            delattr(builtin_mod, self._result_var)
456