490 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			490 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
	
	
"""
 | 
						|
perform ReST syntax, local and remote reference tests on .rst/.txt files. 
 | 
						|
"""
 | 
						|
import py
 | 
						|
 | 
						|
def pytest_addoption(parser):
 | 
						|
    group = parser.addgroup("ReST", "ReST documentation check options")
 | 
						|
    group.addoption('-R', '--urlcheck',
 | 
						|
           action="store_true", dest="urlcheck", default=False, 
 | 
						|
           help="urlopen() remote links found in ReST text files.") 
 | 
						|
    group.addoption('--urltimeout', action="store", metavar="secs",
 | 
						|
        type="int", dest="urlcheck_timeout", default=5,
 | 
						|
        help="timeout in seconds for remote urlchecks")
 | 
						|
    group.addoption('--forcegen',
 | 
						|
           action="store_true", dest="forcegen", default=False,
 | 
						|
           help="force generation of html files.")
 | 
						|
 | 
						|
def pytest_collect_file(path, parent):
 | 
						|
    if path.ext in (".txt", ".rst"):
 | 
						|
        project = getproject(path)
 | 
						|
        if project is not None:
 | 
						|
            return ReSTFile(path, parent=parent, project=project)
 | 
						|
 | 
						|
def getproject(path):
 | 
						|
    for parent in path.parts(reverse=True):
 | 
						|
        confrest = parent.join("confrest.py")
 | 
						|
        if confrest.check():
 | 
						|
            Project = confrest.pyimport().Project
 | 
						|
            return Project(parent)
 | 
						|
 | 
						|
class ReSTFile(py.test.collect.File):
 | 
						|
    def __init__(self, fspath, parent, project=None):
 | 
						|
        super(ReSTFile, self).__init__(fspath=fspath, parent=parent)
 | 
						|
        if project is None:
 | 
						|
            project = getproject(fspath)
 | 
						|
            assert project is not None
 | 
						|
        self.project = project
 | 
						|
 | 
						|
    def collect(self):
 | 
						|
        return [
 | 
						|
            ReSTSyntaxTest(self.project, "ReSTSyntax", parent=self),
 | 
						|
            LinkCheckerMaker("checklinks", parent=self),
 | 
						|
            DoctestText("doctest", parent=self),
 | 
						|
        ]
 | 
						|
 | 
						|
def deindent(s, sep='\n'):
 | 
						|
    leastspaces = -1
 | 
						|
    lines = s.split(sep)
 | 
						|
    for line in lines:
 | 
						|
        if not line.strip():
 | 
						|
            continue
 | 
						|
        spaces = len(line) - len(line.lstrip())
 | 
						|
        if leastspaces == -1 or spaces < leastspaces:
 | 
						|
            leastspaces = spaces
 | 
						|
    if leastspaces == -1:
 | 
						|
        return s
 | 
						|
    for i, line in py.builtin.enumerate(lines):
 | 
						|
        if not line.strip():
 | 
						|
            lines[i] = ''
 | 
						|
        else:
 | 
						|
            lines[i] = line[leastspaces:]
 | 
						|
    return sep.join(lines)
 | 
						|
 | 
						|
class ReSTSyntaxTest(py.test.collect.Item): 
 | 
						|
    def __init__(self, project, *args, **kwargs):
 | 
						|
        super(ReSTSyntaxTest, self).__init__(*args, **kwargs)
 | 
						|
        self.project = project
 | 
						|
 | 
						|
    def reportinfo(self):
 | 
						|
        return self.fspath, None, "syntax check"
 | 
						|
 | 
						|
    def runtest(self):
 | 
						|
        self.restcheck(py.path.svnwc(self.fspath))
 | 
						|
 | 
						|
    def restcheck(self, path):
 | 
						|
        py.test.importorskip("docutils")
 | 
						|
        self.register_linkrole()
 | 
						|
        from docutils.utils import SystemMessage
 | 
						|
        try: 
 | 
						|
            self._checkskip(path, self.project.get_htmloutputpath(path))
 | 
						|
            self.project.process(path)
 | 
						|
        except KeyboardInterrupt: 
 | 
						|
            raise 
 | 
						|
        except SystemMessage: 
 | 
						|
            # we assume docutils printed info on stdout 
 | 
						|
            py.test.fail("docutils processing failed, see captured stderr") 
 | 
						|
 | 
						|
    def register_linkrole(self):
 | 
						|
        from py.__.rest import directive
 | 
						|
        directive.register_linkrole('api', self.resolve_linkrole)
 | 
						|
        directive.register_linkrole('source', self.resolve_linkrole)
 | 
						|
 | 
						|
        # XXX fake sphinx' "toctree" and refs
 | 
						|
        directive.register_linkrole('ref', self.resolve_linkrole)
 | 
						|
        
 | 
						|
        from docutils.parsers.rst import directives
 | 
						|
        def toctree_directive(name, arguments, options, content, lineno,
 | 
						|
                      content_offset, block_text, state, state_machine):
 | 
						|
            return []
 | 
						|
        toctree_directive.content = 1
 | 
						|
        toctree_directive.options = {'maxdepth': int, 'glob': directives.flag,
 | 
						|
                             'hidden': directives.flag}
 | 
						|
        directives.register_directive('toctree', toctree_directive)
 | 
						|
        self.register_pygments()
 | 
						|
 | 
						|
    def register_pygments(self):
 | 
						|
        # taken from pygments-main/external/rst-directive.py 
 | 
						|
        from docutils.parsers.rst import directives
 | 
						|
        try:
 | 
						|
            from pygments.formatters import HtmlFormatter
 | 
						|
        except ImportError:
 | 
						|
            def pygments_directive(name, arguments, options, content, lineno,
 | 
						|
                                   content_offset, block_text, state, state_machine):
 | 
						|
                return []
 | 
						|
            pygments_directive.options = {}
 | 
						|
        else:
 | 
						|
            # The default formatter
 | 
						|
            DEFAULT = HtmlFormatter(noclasses=True)
 | 
						|
            # Add name -> formatter pairs for every variant you want to use
 | 
						|
            VARIANTS = {
 | 
						|
                # 'linenos': HtmlFormatter(noclasses=INLINESTYLES, linenos=True),
 | 
						|
            }
 | 
						|
 | 
						|
            from docutils import nodes
 | 
						|
 | 
						|
            from pygments import highlight
 | 
						|
            from pygments.lexers import get_lexer_by_name, TextLexer
 | 
						|
 | 
						|
            def pygments_directive(name, arguments, options, content, lineno,
 | 
						|
                                   content_offset, block_text, state, state_machine):
 | 
						|
                try:
 | 
						|
                    lexer = get_lexer_by_name(arguments[0])
 | 
						|
                except ValueError:
 | 
						|
                    # no lexer found - use the text one instead of an exception
 | 
						|
                    lexer = TextLexer()
 | 
						|
                # take an arbitrary option if more than one is given
 | 
						|
                formatter = options and VARIANTS[options.keys()[0]] or DEFAULT
 | 
						|
                parsed = highlight(u'\n'.join(content), lexer, formatter)
 | 
						|
                return [nodes.raw('', parsed, format='html')]
 | 
						|
 | 
						|
            pygments_directive.options = dict([(key, directives.flag) for key in VARIANTS])
 | 
						|
 | 
						|
        pygments_directive.arguments = (1, 0, 1)
 | 
						|
        pygments_directive.content = 1
 | 
						|
        directives.register_directive('sourcecode', pygments_directive)
 | 
						|
 | 
						|
    def resolve_linkrole(self, name, text, check=True):
 | 
						|
        apigen_relpath = self.project.apigen_relpath
 | 
						|
    
 | 
						|
        if name == 'api':
 | 
						|
            if text == 'py':
 | 
						|
                return ('py', apigen_relpath + 'api/index.html')
 | 
						|
            else:
 | 
						|
                assert text.startswith('py.'), (
 | 
						|
                    'api link "%s" does not point to the py package') % (text,)
 | 
						|
                dotted_name = text
 | 
						|
                if dotted_name.find('(') > -1:
 | 
						|
                    dotted_name = dotted_name[:text.find('(')]
 | 
						|
                # remove pkg root
 | 
						|
                path = dotted_name.split('.')[1:]
 | 
						|
                dotted_name = '.'.join(path)
 | 
						|
                obj = py
 | 
						|
                if check:
 | 
						|
                    for chunk in path:
 | 
						|
                        try:
 | 
						|
                            obj = getattr(obj, chunk)
 | 
						|
                        except AttributeError:
 | 
						|
                            raise AssertionError(
 | 
						|
                                'problem with linkrole :api:`%s`: can not resolve '
 | 
						|
                                'dotted name %s' % (text, dotted_name,))
 | 
						|
                return (text, apigen_relpath + 'api/%s.html' % (dotted_name,))
 | 
						|
        elif name == 'source':
 | 
						|
            assert text.startswith('py/'), ('source link "%s" does not point '
 | 
						|
                                            'to the py package') % (text,)
 | 
						|
            relpath = '/'.join(text.split('/')[1:])
 | 
						|
            if check:
 | 
						|
                pkgroot = py.__pkg__.getpath()
 | 
						|
                abspath = pkgroot.join(relpath)
 | 
						|
                assert pkgroot.join(relpath).check(), (
 | 
						|
                        'problem with linkrole :source:`%s`: '
 | 
						|
                        'path %s does not exist' % (text, relpath))
 | 
						|
            if relpath.endswith('/') or not relpath:
 | 
						|
                relpath += 'index.html'
 | 
						|
            else:
 | 
						|
                relpath += '.html'
 | 
						|
            return (text, apigen_relpath + 'source/%s' % (relpath,))
 | 
						|
        elif name == 'ref':
 | 
						|
            return ("", "") 
 | 
						|
 | 
						|
    def _checkskip(self, lpath, htmlpath=None):
 | 
						|
        if not self.config.getvalue("forcegen"):
 | 
						|
            lpath = py.path.local(lpath)
 | 
						|
            if htmlpath is not None:
 | 
						|
                htmlpath = py.path.local(htmlpath)
 | 
						|
            if lpath.ext == '.txt': 
 | 
						|
                htmlpath = htmlpath or lpath.new(ext='.html')
 | 
						|
                if htmlpath.check(file=1) and htmlpath.mtime() >= lpath.mtime(): 
 | 
						|
                    py.test.skip("html file is up to date, use --forcegen to regenerate")
 | 
						|
                    #return [] # no need to rebuild 
 | 
						|
 | 
						|
class DoctestText(py.test.collect.Item): 
 | 
						|
    def reportinfo(self):
 | 
						|
        return self.fspath, None, "doctest"
 | 
						|
 | 
						|
    def runtest(self): 
 | 
						|
        content = self._normalize_linesep()
 | 
						|
        newcontent = self.config.hook.pytest_doctest_prepare_content(content=content)
 | 
						|
        if newcontent is not None:
 | 
						|
            content = newcontent 
 | 
						|
        s = content 
 | 
						|
        l = []
 | 
						|
        prefix = '.. >>> '
 | 
						|
        mod = py.std.types.ModuleType(self.fspath.purebasename) 
 | 
						|
        skipchunk = False
 | 
						|
        for line in deindent(s).split('\n'):
 | 
						|
            stripped = line.strip()
 | 
						|
            if skipchunk and line.startswith(skipchunk):
 | 
						|
                print "skipping", line
 | 
						|
                continue
 | 
						|
            skipchunk = False 
 | 
						|
            if stripped.startswith(prefix):
 | 
						|
                try:
 | 
						|
                    exec py.code.Source(stripped[len(prefix):]).compile() in \
 | 
						|
                        mod.__dict__
 | 
						|
                except ValueError, e:
 | 
						|
                    if e.args and e.args[0] == "skipchunk":
 | 
						|
                        skipchunk = " " * (len(line) - len(line.lstrip()))
 | 
						|
                    else:
 | 
						|
                        raise
 | 
						|
            else:
 | 
						|
                l.append(line)
 | 
						|
        docstring = "\n".join(l)
 | 
						|
        mod.__doc__ = docstring 
 | 
						|
        failed, tot = py.compat.doctest.testmod(mod, verbose=1)
 | 
						|
        if failed: 
 | 
						|
            py.test.fail("doctest %s: %s failed out of %s" %(
 | 
						|
                         self.fspath, failed, tot))
 | 
						|
 | 
						|
    def _normalize_linesep(self):
 | 
						|
        # XXX quite nasty... but it works (fixes win32 issues)
 | 
						|
        s = self.fspath.read()
 | 
						|
        linesep = '\n'
 | 
						|
        if '\r' in s:
 | 
						|
            if '\n' not in s:
 | 
						|
                linesep = '\r'
 | 
						|
            else:
 | 
						|
                linesep = '\r\n'
 | 
						|
        s = s.replace(linesep, '\n')
 | 
						|
        return s
 | 
						|
        
 | 
						|
class LinkCheckerMaker(py.test.collect.Collector): 
 | 
						|
    def collect(self):
 | 
						|
        return list(self.genlinkchecks())
 | 
						|
 | 
						|
    def genlinkchecks(self):
 | 
						|
        path = self.fspath
 | 
						|
        # generating functions + args as single tests 
 | 
						|
        timeout = self.config.getvalue("urlcheck_timeout")
 | 
						|
        for lineno, line in py.builtin.enumerate(path.readlines()): 
 | 
						|
            line = line.strip()
 | 
						|
            if line.startswith('.. _'): 
 | 
						|
                if line.startswith('.. _`'):
 | 
						|
                    delim = '`:'
 | 
						|
                else:
 | 
						|
                    delim = ':'
 | 
						|
                l = line.split(delim, 1)
 | 
						|
                if len(l) != 2: 
 | 
						|
                    continue
 | 
						|
                tryfn = l[1].strip() 
 | 
						|
                name = "%s:%d" %(tryfn, lineno)
 | 
						|
                if tryfn.startswith('http:') or tryfn.startswith('https'): 
 | 
						|
                    if self.config.getvalue("urlcheck"):
 | 
						|
                        yield CheckLink(name, parent=self, 
 | 
						|
                            args=(tryfn, path, lineno, timeout), checkfunc=urlcheck)
 | 
						|
                elif tryfn.startswith('webcal:'):
 | 
						|
                    continue
 | 
						|
                else: 
 | 
						|
                    i = tryfn.find('#') 
 | 
						|
                    if i != -1: 
 | 
						|
                        checkfn = tryfn[:i]
 | 
						|
                    else: 
 | 
						|
                        checkfn = tryfn 
 | 
						|
                    if checkfn.strip() and (1 or checkfn.endswith('.html')): 
 | 
						|
                        yield CheckLink(name, parent=self, 
 | 
						|
                            args=(tryfn, path, lineno), checkfunc=localrefcheck)
 | 
						|
        
 | 
						|
class CheckLink(py.test.collect.Item):
 | 
						|
    def __init__(self, name, parent, args, checkfunc):
 | 
						|
        super(CheckLink, self).__init__(name, parent)
 | 
						|
        self.args = args
 | 
						|
        self.checkfunc = checkfunc
 | 
						|
 | 
						|
    def runtest(self):
 | 
						|
        return self.checkfunc(*self.args)
 | 
						|
 | 
						|
    def reportinfo(self, basedir=None):
 | 
						|
        return (self.fspath, self.args[2], "checklink: %s" % self.args[0])
 | 
						|
 | 
						|
def urlcheck(tryfn, path, lineno, TIMEOUT_URLOPEN): 
 | 
						|
    old = py.std.socket.getdefaulttimeout()
 | 
						|
    py.std.socket.setdefaulttimeout(TIMEOUT_URLOPEN)
 | 
						|
    try:
 | 
						|
        try: 
 | 
						|
            print "trying remote", tryfn
 | 
						|
            py.std.urllib2.urlopen(tryfn)
 | 
						|
        finally:
 | 
						|
            py.std.socket.setdefaulttimeout(old)
 | 
						|
    except (py.std.urllib2.URLError, py.std.urllib2.HTTPError), e: 
 | 
						|
        if getattr(e, 'code', None) in (401, 403): # authorization required, forbidden
 | 
						|
            py.test.skip("%s: %s" %(tryfn, str(e)))
 | 
						|
        else:
 | 
						|
            py.test.fail("remote reference error %r in %s:%d\n%s" %(
 | 
						|
                         tryfn, path.basename, lineno+1, e))
 | 
						|
 | 
						|
def localrefcheck(tryfn, path, lineno): 
 | 
						|
    # assume it should be a file 
 | 
						|
    i = tryfn.find('#')
 | 
						|
    if tryfn.startswith('javascript:'):
 | 
						|
        return # don't check JS refs
 | 
						|
    if i != -1: 
 | 
						|
        anchor = tryfn[i+1:]
 | 
						|
        tryfn = tryfn[:i]
 | 
						|
    else: 
 | 
						|
        anchor = ''
 | 
						|
    fn = path.dirpath(tryfn) 
 | 
						|
    ishtml = fn.ext == '.html' 
 | 
						|
    fn = ishtml and fn.new(ext='.txt') or fn
 | 
						|
    print "filename is", fn 
 | 
						|
    if not fn.check(): # not ishtml or not fn.check(): 
 | 
						|
        if not py.path.local(tryfn).check(): # the html could be there 
 | 
						|
            py.test.fail("reference error %r in %s:%d" %(
 | 
						|
                          tryfn, path.basename, lineno+1))
 | 
						|
    if anchor: 
 | 
						|
        source = unicode(fn.read(), 'latin1')
 | 
						|
        source = source.lower().replace('-', ' ') # aehem
 | 
						|
 | 
						|
        anchor = anchor.replace('-', ' ') 
 | 
						|
        match2 = ".. _`%s`:" % anchor 
 | 
						|
        match3 = ".. _%s:" % anchor 
 | 
						|
        candidates = (anchor, match2, match3)
 | 
						|
        print "candidates", repr(candidates)
 | 
						|
        for line in source.split('\n'): 
 | 
						|
            line = line.strip()
 | 
						|
            if line in candidates: 
 | 
						|
                break 
 | 
						|
        else: 
 | 
						|
            py.test.fail("anchor reference error %s#%s in %s:%d" %(
 | 
						|
                tryfn, anchor, path.basename, lineno+1))
 | 
						|
 | 
						|
 | 
						|
#
 | 
						|
# PLUGIN tests
 | 
						|
#
 | 
						|
 | 
						|
def test_deindent():
 | 
						|
    assert deindent('foo') == 'foo'
 | 
						|
    assert deindent('foo\n  bar') == 'foo\n  bar'
 | 
						|
    assert deindent('  foo\n  bar\n') == 'foo\nbar\n'
 | 
						|
    assert deindent('  foo\n\n  bar\n') == 'foo\n\nbar\n'
 | 
						|
    assert deindent(' foo\n  bar\n') == 'foo\n bar\n'
 | 
						|
    assert deindent('  foo\n bar\n') == ' foo\nbar\n'
 | 
						|
 | 
						|
class TestApigenLinkRole:
 | 
						|
    disabled = True
 | 
						|
 | 
						|
    # these tests are moved here from the former py/doc/conftest.py
 | 
						|
    def test_resolve_linkrole(self):
 | 
						|
        from py.__.doc.conftest import get_apigen_relpath
 | 
						|
        apigen_relpath = get_apigen_relpath()
 | 
						|
 | 
						|
        assert resolve_linkrole('api', 'py.foo.bar', False) == (
 | 
						|
            'py.foo.bar', apigen_relpath + 'api/foo.bar.html')
 | 
						|
        assert resolve_linkrole('api', 'py.foo.bar()', False) == (
 | 
						|
            'py.foo.bar()', apigen_relpath + 'api/foo.bar.html')
 | 
						|
        assert resolve_linkrole('api', 'py', False) == (
 | 
						|
            'py', apigen_relpath + 'api/index.html')
 | 
						|
        py.test.raises(AssertionError, 'resolve_linkrole("api", "foo.bar")')
 | 
						|
        assert resolve_linkrole('source', 'py/foo/bar.py', False) == (
 | 
						|
            'py/foo/bar.py', apigen_relpath + 'source/foo/bar.py.html')
 | 
						|
        assert resolve_linkrole('source', 'py/foo/', False) == (
 | 
						|
            'py/foo/', apigen_relpath + 'source/foo/index.html')
 | 
						|
        assert resolve_linkrole('source', 'py/', False) == (
 | 
						|
            'py/', apigen_relpath + 'source/index.html')
 | 
						|
        py.test.raises(AssertionError, 'resolve_linkrole("source", "/foo/bar/")')
 | 
						|
 | 
						|
    def test_resolve_linkrole_check_api(self):
 | 
						|
        assert resolve_linkrole('api', 'py.test.ensuretemp')
 | 
						|
        py.test.raises(AssertionError, "resolve_linkrole('api', 'py.foo.baz')")
 | 
						|
 | 
						|
    def test_resolve_linkrole_check_source(self):
 | 
						|
        assert resolve_linkrole('source', 'py/path/common.py')
 | 
						|
        py.test.raises(AssertionError,
 | 
						|
                       "resolve_linkrole('source', 'py/foo/bar.py')")
 | 
						|
 | 
						|
 | 
						|
class TestDoctest:
 | 
						|
    def pytest_funcarg__testdir(self, request):
 | 
						|
        testdir = request.getfuncargvalue("testdir")
 | 
						|
        assert request.module.__name__ == __name__
 | 
						|
        testdir.makepyfile(confrest="from py.__.misc.rest import Project")
 | 
						|
        for p in testdir.plugins:
 | 
						|
            if p == globals():
 | 
						|
                break
 | 
						|
        else:
 | 
						|
            testdir.plugins.append(globals())
 | 
						|
        return testdir
 | 
						|
    
 | 
						|
    def test_doctest_extra_exec(self, testdir):
 | 
						|
        xtxt = testdir.maketxtfile(x="""
 | 
						|
            hello::
 | 
						|
                .. >>> raise ValueError 
 | 
						|
                   >>> None
 | 
						|
        """)
 | 
						|
        reprec = testdir.inline_run(xtxt)
 | 
						|
        passed, skipped, failed = reprec.countoutcomes()
 | 
						|
        assert failed == 1
 | 
						|
 | 
						|
    def test_doctest_basic(self, testdir): 
 | 
						|
        xtxt = testdir.maketxtfile(x="""
 | 
						|
            .. 
 | 
						|
               >>> from os.path import abspath 
 | 
						|
 | 
						|
            hello world 
 | 
						|
 | 
						|
               >>> assert abspath 
 | 
						|
               >>> i=3
 | 
						|
               >>> print i
 | 
						|
               3
 | 
						|
 | 
						|
            yes yes
 | 
						|
 | 
						|
                >>> i
 | 
						|
                3
 | 
						|
 | 
						|
            end
 | 
						|
        """)
 | 
						|
        reprec = testdir.inline_run(xtxt)
 | 
						|
        passed, skipped, failed = reprec.countoutcomes()
 | 
						|
        assert failed == 0 
 | 
						|
        assert passed + skipped == 2
 | 
						|
 | 
						|
    def test_doctest_eol(self, testdir): 
 | 
						|
        ytxt = testdir.maketxtfile(y=".. >>> 1 + 1\r\n   2\r\n\r\n")
 | 
						|
        reprec = testdir.inline_run(ytxt)
 | 
						|
        passed, skipped, failed = reprec.countoutcomes()
 | 
						|
        assert failed == 0 
 | 
						|
        assert passed + skipped == 2
 | 
						|
 | 
						|
    def test_doctest_indentation(self, testdir):
 | 
						|
        footxt = testdir.maketxtfile(foo=
 | 
						|
            '..\n  >>> print "foo\\n  bar"\n  foo\n    bar\n')
 | 
						|
        reprec = testdir.inline_run(footxt)
 | 
						|
        passed, skipped, failed = reprec.countoutcomes()
 | 
						|
        assert failed == 0
 | 
						|
        assert skipped + passed == 2 
 | 
						|
 | 
						|
    def test_js_ignore(self, testdir):
 | 
						|
        xtxt = testdir.maketxtfile(xtxt="""
 | 
						|
            `blah`_
 | 
						|
 | 
						|
            .. _`blah`: javascript:some_function()
 | 
						|
        """)
 | 
						|
        reprec = testdir.inline_run(xtxt)
 | 
						|
        passed, skipped, failed = reprec.countoutcomes()
 | 
						|
        assert failed == 0
 | 
						|
        assert skipped + passed == 3 
 | 
						|
 | 
						|
    def test_pytest_doctest_prepare_content(self, testdir):
 | 
						|
        l = []
 | 
						|
        class MyPlugin:
 | 
						|
            def pytest_doctest_prepare_content(self, content):
 | 
						|
                l.append(content)
 | 
						|
                return content.replace("False", "True")
 | 
						|
 | 
						|
        testdir.plugins.append(MyPlugin())
 | 
						|
 | 
						|
        xtxt = testdir.maketxtfile(x="""
 | 
						|
            hello:
 | 
						|
 | 
						|
                >>> 2 == 2
 | 
						|
                False
 | 
						|
 | 
						|
        """)
 | 
						|
        reprec = testdir.inline_run(xtxt)
 | 
						|
        assert len(l) == 1
 | 
						|
        passed, skipped, failed = reprec.countoutcomes()
 | 
						|
        assert passed >= 1
 | 
						|
        assert not failed 
 | 
						|
        assert skipped <= 1
 |