1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
|
# -*- coding: utf-8 -*-
# Copyright (c) 2011, Sebastian Wiesner <lunaryorn@googlemail.com>
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import (print_function, division, unicode_literals,
absolute_import)
import os
import pytest
import py.path
from mock import Mock
from pyquery import PyQuery
from sphinx.application import Sphinx
from sphinx.builders.html import StandaloneHTMLBuilder
from sphinx.environment import SphinxStandaloneReader
from sphinxcontrib.issuetracker import Issue, IssuesReferences
TEST_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
def assert_issue_reference(doctree, issue, title=False):
"""
pytest helper which asserts that the given ``doctree`` contains a single
reference, which references the given ``issue``.
If ``title`` is ``True``, it is expected that the reference text is the
issue title, otherwise (the default) it is expected that the reference text
is the issue id.
Return the reference node. Raise :exc:`~exceptions.AssertionError` if the
``doctree`` doesn't contain a reference to the given ``issue``.
"""
__tracebackhide__ = True
reference = doctree.find('reference')
assert len(reference) == 1
assert reference.attr.refuri == issue.url
classes = reference.attr.classes.split(' ')
is_closed = 'issue-closed' in classes
assert 'reference-issue' in classes
assert issue.closed == is_closed
if title:
assert reference.text() == issue.title
else:
assert reference.text() == '#{0}'.format(issue.id)
return reference
def get_index_doctree(app):
"""
Get the doctree of the index document processed by the given ``app`` as
XML.
``app`` is the sphinx application from which to get the doctree.
Return a :class:`~pyquery.PyQuery` object representing the doctree.
"""
return PyQuery(str(app.env.get_doctree('index')), parser='xml')
def pytest_namespace():
"""
Add the following functions to the pytest namespace:
- :func:`get_index_doctree`
- :func:`assert_issue_reference`
"""
return dict((f.__name__, f) for f in
(get_index_doctree, assert_issue_reference))
def pytest_configure(config):
"""
Configure issue tracker tests.
Adds ``confpy`` attribute to ``config`` which provides the path to the test
``conf.py`` file.
"""
config.confpy = py.path.local(TEST_DIRECTORY).join('conf.py')
def pytest_funcarg__content(request):
"""
The content for the test document as string.
By default, the content is taken from the argument of the ``with_content``
marker. If no such marker exists, the content is build from the id
returned by the ``issue_id`` funcargby prepending a dash before the id.
The issue id ``'10'`` will thus produce the content ``'#10'``. If the
``issue_id`` funcarg returns ``None``, a :exc:`~exceptions.ValueError` is
raised eventually.
Test modules may override this funcarg to add their own content.
"""
content_mark = request.keywords.get('with_content')
if content_mark:
return content_mark.args[0]
else:
issue_id = request.getfuncargvalue('issue_id')
if issue_id:
return '#{0}'.format(issue_id)
raise ValueError('no content provided')
def pytest_funcarg__doctree(request):
"""
The doctree of the parsed and processed ``content`` as
:class:`~pyquery.PyQuery` object.
.. note::
This funcarg automatically builds the application to create the doctree.
This happens *before* test execution! If you need to build inside the
test, build manually and use :func:`get_index_doctree()` to get the
doctree afterwards.
"""
app = request.getfuncargvalue('app')
app.build()
return pytest.get_index_doctree(app)
def pytest_funcarg__srcdir(request):
"""
The Sphinx source directory for the current test as path.
This directory contains the standard test ``conf.py`` and a single document
named ``index.rst``. The content of this document is the return value of
the ``content`` funcarg.
"""
tmpdir = request.getfuncargvalue('tmpdir')
srcdir = tmpdir.join('src')
srcdir.ensure(dir=True)
confpy = request.getfuncargvalue('pytestconfig').confpy
confpy.copy(srcdir)
content = request.getfuncargvalue('content')
srcdir.join('index.rst').write(content)
return srcdir
def pytest_funcarg__outdir(request):
"""
The Sphinx output directory for the current test as path.
"""
tmpdir = request.getfuncargvalue('tmpdir')
return tmpdir.join('html')
def pytest_funcarg__doctreedir(request):
"""
The Sphinx doctree directory for the current test as path.
"""
tmpdir = request.getfuncargvalue('tmpdir')
return tmpdir.join('doctrees')
def reset_global_state():
"""
Remove global state setup by Sphinx.
Makes sure that we got a fresh test application for each test.
"""
SphinxStandaloneReader.transforms.remove(IssuesReferences)
StandaloneHTMLBuilder.css_files.remove('_static/issuetracker.css')
def pytest_funcarg__confoverrides(request):
"""
Configuration value overrides for the current test as dictionary.
By default this funcarg takes the configuration overrides from the keyword
arguments of the ``confoverrides`` marker. If the marker doesn't exist,
an empty dictionary is returned.
Test modules may override this funcarg to return custom ``confoverrides``.
"""
confoverrides_marker = request.keywords.get('confoverrides')
return confoverrides_marker.kwargs if confoverrides_marker else {}
def pytest_funcarg__app(request):
"""
A Sphinx application for testing.
The app uses the source directory from the ``srcdir`` funcarg, and writes
to the directories given by the ``outdir`` and ``doctreedir`` funcargs.
Additional configuration values can be inserted into this application
through the ``confoverrides`` funcarg.
If the marker ``mock_resolver`` is attached to the current test, the
resolver callback returned by the ``mock_resolver`` funcarg is
automatically connected to the ``issuetracker-resolve-issue`` event in the
the created application.
If the marker ``build_app`` is attached to the current test, the app is
build before returning it. Otherwise you need to build explicitly in order
to get the output.
"""
srcdir = request.getfuncargvalue('srcdir')
outdir = request.getfuncargvalue('outdir')
doctreedir = request.getfuncargvalue('doctreedir')
confoverrides = request.getfuncargvalue('confoverrides')
app = Sphinx(str(srcdir), str(srcdir), str(outdir), str(doctreedir),
'html',confoverrides=confoverrides, status=None, warning=None,
freshenv=True)
request.addfinalizer(reset_global_state)
if 'mock_resolver' in request.keywords:
lookup_mock_issue = request.getfuncargvalue('mock_resolver')
app.connect(b'issuetracker-resolve-issue', lookup_mock_issue)
if 'build_app' in request.keywords:
app.build()
return app
def pytest_funcarg__issue(request):
"""
An :class:`~sphinxcontrib.issuetracker.Issue` for the current test, or
``None``, if no issue is to be used.
By default, this funcarg creates an issue from the arguments of the
``with_issue`` marker, or returns ``None``, if there is no such marker on
the current test.
Test modules may override this funcarg to provide their own issues for
tests.
"""
issue_marker = request.keywords.get('with_issue')
if issue_marker:
return Issue(*issue_marker.args, **issue_marker.kwargs)
return None
def pytest_funcarg__issue_id(request):
"""
The issue id for the current test, or ``None``, if no issue id is to be
used.
The issue id is taken from the ``id`` attribute of the issue returned by
the ``issue`` funcarg. If the ``issue`` funcarg returns ``None``, this
funcarg also returns ``None``.
"""
issue = request.getfuncargvalue('issue')
if issue:
return issue.id
else:
return None
def pytest_funcarg__mock_resolver(request):
"""
A mocked callback for the ``issuetracker-resolve-issue`` event as
:class:`~mock.Mock` object.
If the ``issue`` funcarg doesn't return ``None``, the callback will return
this issue if the issue id given to the callback matches the id of this
issue. Otherwise it will always return ``None``.
"""
lookup_mock_issue = Mock(name='lookup_mock_issue', return_value=None)
issue = request.getfuncargvalue('issue')
if issue:
def lookup(app, tracker_config, issue_id):
return issue if issue_id == issue.id else None
lookup_mock_issue.side_effect = lookup
return lookup_mock_issue
|