view tests/test-doctest.py @ 6664:e20841870e51 stable

tests: show extra dict of some changesets to be able to see change The next changeset will change the extra dict of some of these changesets. To better see how they change, show them. This will be removed again later.
author Manuel Jacob <me@manueljacob.de>
date Thu, 01 Feb 2024 08:29:10 +0100
parents 7ad8107d953a
children 16fd24f6cf22
line wrap: on
line source

# this is hack to make sure no escape characters are inserted into the output

from __future__ import absolute_import
from __future__ import print_function

import doctest
import os
import re
import subprocess
import sys

ispy3 = sys.version_info[0] >= 3

if 'TERM' in os.environ:
    del os.environ['TERM']


class py3docchecker(doctest.OutputChecker):
    def check_output(self, want, got, optionflags):
        want2 = re.sub(r'''\bu(['"])(.*?)\1''', r'\1\2\1', want)  # py2: u''
        got2 = re.sub(r'''\bb(['"])(.*?)\1''', r'\1\2\1', got)  # py3: b''
        # py3: <exc.name>: b'<msg>' -> <name>: <msg>
        #      <exc.name>: <others> -> <name>: <others>
        # TODO: more output massaging
        return any(
            doctest.OutputChecker.check_output(self, w, g, optionflags)
            for w, g in [(want, got), (want2, got2)]
        )


def testmod(name, optionflags=0, testtarget=None):
    __import__(name)
    mod = sys.modules[name]
    if testtarget is not None:
        mod = getattr(mod, testtarget)

    # minimal copy of doctest.testmod()
    finder = doctest.DocTestFinder()
    checker = None
    if ispy3:
        checker = py3docchecker()
    runner = doctest.DocTestRunner(checker=checker, optionflags=optionflags)
    for test in finder.find(mod, name):
        runner.run(test)
    runner.summarize()


DONT_RUN = []

# Exceptions to the defaults for a given detected module. The value for each
# module name is a list of dicts that specify the kwargs to pass to testmod.
# testmod is called once per item in the list, so an empty list will cause the
# module to not be tested.
testmod_arg_overrides = {
    # 'mercurial.statprof': DONT_RUN,  # >>> is an example, not a doctest
}

fileset = 'set:(**.py)'

cwd = os.path.dirname(os.environ["TESTDIR"])

# run-tests.py makes an installation of core Mercurial in /tmp/, but evolve is
# not installed together with it, and evolve modules need to be imported to run
# doctests. We insert it at the start to make sure wider install of evolve does not take priority.
sys.path.insert(0, cwd)

if not os.path.isdir(os.path.join(cwd, ".hg")):
    sys.exit(0)

files = subprocess.check_output(
    "hg files --print0 \"%s\"" % fileset,
    shell=True,
    cwd=cwd,
).split(b'\0')

if sys.version_info[0] >= 3:
    cwd = os.fsencode(cwd)

mods_tested = set()
for f in files:
    if not f:
        continue

    with open(os.path.join(cwd, f), "rb") as fh:
        if not re.search(br'\n\s*>>>', fh.read()):
            continue

    if ispy3:
        f = f.decode()

    modname = f.replace('.py', '').replace('\\', '.').replace('/', '.')

    # Third-party modules aren't our responsibility to test.
    if modname.startswith('hgext3rd.evolve.thirdparty.'):
        continue

    for kwargs in testmod_arg_overrides.get(modname, [{}]):
        mods_tested.add((modname, '%r' % (kwargs,)))
        if modname.startswith('tests.'):
            # On py2, we can't import from tests.foo, but it works on both py2
            # and py3 with the way that PYTHONPATH is setup to import without
            # the 'tests.' prefix, so we do that.
            modname = modname[len('tests.'):]

        testmod(modname, **kwargs)

# Meta-test: let's make sure that we actually ran what we expected to, above.
# Each item in the set is a 2-tuple of module name and stringified kwargs passed
# to testmod.
expected_mods_tested = set(
    [
        ('hgext3rd.evolve.obshistory', '{}'),
        ('hgext3rd.topic.common', '{}'),
    ]
)

unexpectedly_run = mods_tested.difference(expected_mods_tested)
not_run = expected_mods_tested.difference(mods_tested)

if unexpectedly_run:
    print('Unexpectedly ran (probably need to add to list):')
    for r in sorted(unexpectedly_run):
        print('  %r' % (r,))
if not_run:
    print('Expected to run, but was not run (doctest removed?):')
    for r in sorted(not_run):
        print('  %r' % (r,))