summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorGeorg Brandl <georg@python.org>2009-03-16 23:56:42 +0100
committerGeorg Brandl <georg@python.org>2009-03-16 23:56:42 +0100
commitf6e56aa1a6476048d93897a2e5a20aa376fcf1b4 (patch)
tree3ccec41501f25e8386d626a52d8d7da09040ecdc /tests
parent73d7e7c709394974494fb6000c6274f07e79cce5 (diff)
parenta1691a3424ae6b91743db7f37cf4fe86f7c93760 (diff)
downloadsphinx-0.6b1.tar.gz
merge with 0.50.6b1
Diffstat (limited to 'tests')
-rwxr-xr-xtests/coverage.py1163
-rw-r--r--tests/root/_templates/layout.html1
-rw-r--r--tests/root/autodoc.txt21
-rw-r--r--tests/root/autosummary.txt7
-rw-r--r--tests/root/conf.py166
-rw-r--r--tests/root/contents.txt5
-rw-r--r--tests/root/desc.txt11
-rw-r--r--tests/root/images.txt3
-rw-r--r--tests/root/includes.txt30
-rw-r--r--tests/root/literal.inc9
-rw-r--r--tests/root/markup.txt39
-rw-r--r--tests/root/rimg.pngbin0 -> 218 bytes
-rw-r--r--tests/root/special/code.py2
-rw-r--r--tests/root/subdir/images.txt2
-rw-r--r--tests/root/subdir/includes.txt12
-rw-r--r--tests/root/svgimg.pdfbin0 -> 141783 bytes
-rw-r--r--tests/root/svgimg.svg158
-rw-r--r--tests/root/testtheme/layout.html5
-rw-r--r--tests/root/testtheme/static/staticimg.pngbin0 -> 218 bytes
-rw-r--r--tests/root/testtheme/static/statictmpl.html_t2
-rw-r--r--tests/root/testtheme/theme.conf7
-rw-r--r--tests/root/ziptheme.zipbin0 -> 1039 bytes
-rw-r--r--tests/test_autodoc.py398
-rw-r--r--tests/test_build.py146
-rw-r--r--tests/test_config.py6
-rw-r--r--tests/test_env.py55
-rw-r--r--tests/test_highlighting.py75
-rw-r--r--tests/test_markup.py31
-rw-r--r--tests/test_quickstart.py27
-rw-r--r--tests/test_theming.py56
-rw-r--r--tests/util.py42
31 files changed, 2098 insertions, 381 deletions
diff --git a/tests/coverage.py b/tests/coverage.py
new file mode 100755
index 00000000..148e573c
--- /dev/null
+++ b/tests/coverage.py
@@ -0,0 +1,1163 @@
+#!/usr/bin/python
+#
+# Perforce Defect Tracking Integration Project
+# <http://www.ravenbrook.com/project/p4dti/>
+#
+# COVERAGE.PY -- COVERAGE TESTING
+#
+# Gareth Rees, Ravenbrook Limited, 2001-12-04
+# Ned Batchelder, 2004-12-12
+# http://nedbatchelder.com/code/modules/coverage.html
+#
+#
+# 1. INTRODUCTION
+#
+# This module provides coverage testing for Python code.
+#
+# The intended readership is all Python developers.
+#
+# This document is not confidential.
+#
+# See [GDR 2001-12-04a] for the command-line interface, programmatic
+# interface and limitations. See [GDR 2001-12-04b] for requirements and
+# design.
+
+r"""Usage:
+
+coverage.py -x [-p] MODULE.py [ARG1 ARG2 ...]
+ Execute module, passing the given command-line arguments, collecting
+ coverage data. With the -p option, write to a temporary file containing
+ the machine name and process ID.
+
+coverage.py -e
+ Erase collected coverage data.
+
+coverage.py -c
+ Collect data from multiple coverage files (as created by -p option above)
+ and store it into a single file representing the union of the coverage.
+
+coverage.py -r [-m] [-o dir1,dir2,...] FILE1 FILE2 ...
+ Report on the statement coverage for the given files. With the -m
+ option, show line numbers of the statements that weren't executed.
+
+coverage.py -a [-d dir] [-o dir1,dir2,...] FILE1 FILE2 ...
+ Make annotated copies of the given files, marking statements that
+ are executed with > and statements that are missed with !. With
+ the -d option, make the copies in that directory. Without the -d
+ option, make each copy in the same directory as the original.
+
+-o dir,dir2,...
+ Omit reporting or annotating files when their filename path starts with
+ a directory listed in the omit list.
+ e.g. python coverage.py -i -r -o c:\python23,lib\enthought\traits
+
+Coverage data is saved in the file .coverage by default. Set the
+COVERAGE_FILE environment variable to save it somewhere else."""
+
+__version__ = "2.85.20080914" # see detailed history at the end of this file.
+
+import compiler
+import compiler.visitor
+import glob
+import os
+import re
+import string
+import symbol
+import sys
+import threading
+import token
+import types
+import zipimport
+from socket import gethostname
+
+# Python version compatibility
+try:
+ strclass = basestring # new to 2.3
+except:
+ strclass = str
+
+# 2. IMPLEMENTATION
+#
+# This uses the "singleton" pattern.
+#
+# The word "morf" means a module object (from which the source file can
+# be deduced by suitable manipulation of the __file__ attribute) or a
+# filename.
+#
+# When we generate a coverage report we have to canonicalize every
+# filename in the coverage dictionary just in case it refers to the
+# module we are reporting on. It seems a shame to throw away this
+# information so the data in the coverage dictionary is transferred to
+# the 'cexecuted' dictionary under the canonical filenames.
+#
+# The coverage dictionary is called "c" and the trace function "t". The
+# reason for these short names is that Python looks up variables by name
+# at runtime and so execution time depends on the length of variables!
+# In the bottleneck of this application it's appropriate to abbreviate
+# names to increase speed.
+
+class StatementFindingAstVisitor(compiler.visitor.ASTVisitor):
+ """ A visitor for a parsed Abstract Syntax Tree which finds executable
+ statements.
+ """
+ def __init__(self, statements, excluded, suite_spots):
+ compiler.visitor.ASTVisitor.__init__(self)
+ self.statements = statements
+ self.excluded = excluded
+ self.suite_spots = suite_spots
+ self.excluding_suite = 0
+
+ def doRecursive(self, node):
+ for n in node.getChildNodes():
+ self.dispatch(n)
+
+ visitStmt = visitModule = doRecursive
+
+ def doCode(self, node):
+ if hasattr(node, 'decorators') and node.decorators:
+ self.dispatch(node.decorators)
+ self.recordAndDispatch(node.code)
+ else:
+ self.doSuite(node, node.code)
+
+ visitFunction = visitClass = doCode
+
+ def getFirstLine(self, node):
+ # Find the first line in the tree node.
+ lineno = node.lineno
+ for n in node.getChildNodes():
+ f = self.getFirstLine(n)
+ if lineno and f:
+ lineno = min(lineno, f)
+ else:
+ lineno = lineno or f
+ return lineno
+
+ def getLastLine(self, node):
+ # Find the first line in the tree node.
+ lineno = node.lineno
+ for n in node.getChildNodes():
+ lineno = max(lineno, self.getLastLine(n))
+ return lineno
+
+ def doStatement(self, node):
+ self.recordLine(self.getFirstLine(node))
+
+ visitAssert = visitAssign = visitAssTuple = visitPrint = \
+ visitPrintnl = visitRaise = visitSubscript = visitDecorators = \
+ doStatement
+
+ def visitPass(self, node):
+ # Pass statements have weird interactions with docstrings. If this
+ # pass statement is part of one of those pairs, claim that the statement
+ # is on the later of the two lines.
+ l = node.lineno
+ if l:
+ lines = self.suite_spots.get(l, [l,l])
+ self.statements[lines[1]] = 1
+
+ def visitDiscard(self, node):
+ # Discard nodes are statements that execute an expression, but then
+ # discard the results. This includes function calls, so we can't
+ # ignore them all. But if the expression is a constant, the statement
+ # won't be "executed", so don't count it now.
+ if node.expr.__class__.__name__ != 'Const':
+ self.doStatement(node)
+
+ def recordNodeLine(self, node):
+ # Stmt nodes often have None, but shouldn't claim the first line of
+ # their children (because the first child might be an ignorable line
+ # like "global a").
+ if node.__class__.__name__ != 'Stmt':
+ return self.recordLine(self.getFirstLine(node))
+ else:
+ return 0
+
+ def recordLine(self, lineno):
+ # Returns a bool, whether the line is included or excluded.
+ if lineno:
+ # Multi-line tests introducing suites have to get charged to their
+ # keyword.
+ if lineno in self.suite_spots:
+ lineno = self.suite_spots[lineno][0]
+ # If we're inside an excluded suite, record that this line was
+ # excluded.
+ if self.excluding_suite:
+ self.excluded[lineno] = 1
+ return 0
+ # If this line is excluded, or suite_spots maps this line to
+ # another line that is exlcuded, then we're excluded.
+ elif self.excluded.has_key(lineno) or \
+ self.suite_spots.has_key(lineno) and \
+ self.excluded.has_key(self.suite_spots[lineno][1]):
+ return 0
+ # Otherwise, this is an executable line.
+ else:
+ self.statements[lineno] = 1
+ return 1
+ return 0
+
+ default = recordNodeLine
+
+ def recordAndDispatch(self, node):
+ self.recordNodeLine(node)
+ self.dispatch(node)
+
+ def doSuite(self, intro, body, exclude=0):
+ exsuite = self.excluding_suite
+ if exclude or (intro and not self.recordNodeLine(intro)):
+ self.excluding_suite = 1
+ self.recordAndDispatch(body)
+ self.excluding_suite = exsuite
+
+ def doPlainWordSuite(self, prevsuite, suite):
+ # Finding the exclude lines for else's is tricky, because they aren't
+ # present in the compiler parse tree. Look at the previous suite,
+ # and find its last line. If any line between there and the else's
+ # first line are excluded, then we exclude the else.
+ lastprev = self.getLastLine(prevsuite)
+ firstelse = self.getFirstLine(suite)
+ for l in range(lastprev+1, firstelse):
+ if self.suite_spots.has_key(l):
+ self.doSuite(None, suite, exclude=self.excluded.has_key(l))
+ break
+ else:
+ self.doSuite(None, suite)
+
+ def doElse(self, prevsuite, node):
+ if node.else_:
+ self.doPlainWordSuite(prevsuite, node.else_)
+
+ def visitFor(self, node):
+ self.doSuite(node, node.body)
+ self.doElse(node.body, node)
+
+ visitWhile = visitFor
+
+ def visitIf(self, node):
+ # The first test has to be handled separately from the rest.
+ # The first test is credited to the line with the "if", but the others
+ # are credited to the line with the test for the elif.
+ self.doSuite(node, node.tests[0][1])
+ for t, n in node.tests[1:]:
+ self.doSuite(t, n)
+ self.doElse(node.tests[-1][1], node)
+
+ def visitTryExcept(self, node):
+ self.doSuite(node, node.body)
+ for i in range(len(node.handlers)):
+ a, b, h = node.handlers[i]
+ if not a:
+ # It's a plain "except:". Find the previous suite.
+ if i > 0:
+ prev = node.handlers[i-1][2]
+ else:
+ prev = node.body
+ self.doPlainWordSuite(prev, h)
+ else:
+ self.doSuite(a, h)
+ self.doElse(node.handlers[-1][2], node)
+
+ def visitTryFinally(self, node):
+ self.doSuite(node, node.body)
+ self.doPlainWordSuite(node.body, node.final)
+
+ def visitWith(self, node):
+ self.doSuite(node, node.body)
+
+ def visitGlobal(self, node):
+ # "global" statements don't execute like others (they don't call the
+ # trace function), so don't record their line numbers.
+ pass
+
+the_coverage = None
+
+class CoverageException(Exception):
+ pass
+
+class coverage:
+ # Name of the cache file (unless environment variable is set).
+ cache_default = ".coverage"
+
+ # Environment variable naming the cache file.
+ cache_env = "COVERAGE_FILE"
+
+ # A dictionary with an entry for (Python source file name, line number
+ # in that file) if that line has been executed.
+ c = {}
+
+ # A map from canonical Python source file name to a dictionary in
+ # which there's an entry for each line number that has been
+ # executed.
+ cexecuted = {}
+
+ # Cache of results of calling the analysis2() method, so that you can
+ # specify both -r and -a without doing double work.
+ analysis_cache = {}
+
+ # Cache of results of calling the canonical_filename() method, to
+ # avoid duplicating work.
+ canonical_filename_cache = {}
+
+ def __init__(self):
+ global the_coverage
+ if the_coverage:
+ raise CoverageException("Only one coverage object allowed.")
+ self.usecache = 1
+ self.cache = None
+ self.parallel_mode = False
+ self.exclude_re = ''
+ self.nesting = 0
+ self.cstack = []
+ self.xstack = []
+ self.relative_dir = self.abs_file(os.curdir)+os.sep
+ self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]')
+
+ # t(f, x, y). This method is passed to sys.settrace as a trace function.
+ # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and
+ # the arguments and return value of the trace function.
+ # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code
+ # objects.
+
+ def t(self, f, w, unused): #pragma: no cover
+ if w == 'line':
+ self.c[(f.f_code.co_filename, f.f_lineno)] = 1
+ #-for c in self.cstack:
+ #- c[(f.f_code.co_filename, f.f_lineno)] = 1
+ return self.t
+
+ def help(self, error=None): #pragma: no cover
+ if error:
+ print error
+ print
+ print __doc__
+ sys.exit(1)
+
+ def command_line(self, argv, help_fn=None):
+ import getopt
+ help_fn = help_fn or self.help
+ settings = {}
+ optmap = {
+ '-a': 'annotate',
+ '-c': 'collect',
+ '-d:': 'directory=',
+ '-e': 'erase',
+ '-h': 'help',
+ '-i': 'ignore-errors',
+ '-m': 'show-missing',
+ '-p': 'parallel-mode',
+ '-r': 'report',
+ '-x': 'execute',
+ '-o:': 'omit=',
+ }
+ short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '')
+ long_opts = optmap.values()
+ options, args = getopt.getopt(argv, short_opts, long_opts)
+ for o, a in options:
+ if optmap.has_key(o):
+ settings[optmap[o]] = 1
+ elif optmap.has_key(o + ':'):
+ settings[optmap[o + ':']] = a
+ elif o[2:] in long_opts:
+ settings[o[2:]] = 1
+ elif o[2:] + '=' in long_opts:
+ settings[o[2:]+'='] = a
+ else: #pragma: no cover
+ pass # Can't get here, because getopt won't return anything unknown.
+
+ if settings.get('help'):
+ help_fn()
+
+ for i in ['erase', 'execute']:
+ for j in ['annotate', 'report', 'collect']:
+ if settings.get(i) and settings.get(j):
+ help_fn("You can't specify the '%s' and '%s' "
+ "options at the same time." % (i, j))
+
+ args_needed = (settings.get('execute')
+ or settings.get('annotate')
+ or settings.get('report'))
+ action = (settings.get('erase')
+ or settings.get('collect')
+ or args_needed)
+ if not action:
+ help_fn("You must specify at least one of -e, -x, -c, -r, or -a.")
+ if not args_needed and args:
+ help_fn("Unexpected arguments: %s" % " ".join(args))
+
+ self.parallel_mode = settings.get('parallel-mode')
+ self.get_ready()
+
+ if settings.get('erase'):
+ self.erase()
+ if settings.get('execute'):
+ if not args:
+ help_fn("Nothing to do.")
+ sys.argv = args
+ self.start()
+ import __main__
+ sys.path[0] = os.path.dirname(sys.argv[0])
+ execfile(sys.argv[0], __main__.__dict__)
+ if settings.get('collect'):
+ self.collect()
+ if not args:
+ args = self.cexecuted.keys()
+
+ ignore_errors = settings.get('ignore-errors')
+ show_missing = settings.get('show-missing')
+ directory = settings.get('directory=')
+
+ omit = settings.get('omit=')
+ if omit is not None:
+ omit = [self.abs_file(p) for p in omit.split(',')]
+ else:
+ omit = []
+
+ if settings.get('report'):
+ self.report(args, show_missing, ignore_errors, omit_prefixes=omit)
+ if settings.get('annotate'):
+ self.annotate(args, directory, ignore_errors, omit_prefixes=omit)
+
+ def use_cache(self, usecache, cache_file=None):
+ self.usecache = usecache
+ if cache_file and not self.cache:
+ self.cache_default = cache_file
+
+ def get_ready(self, parallel_mode=False):
+ if self.usecache and not self.cache:
+ self.cache = os.environ.get(self.cache_env, self.cache_default)
+ if self.parallel_mode:
+ self.cache += "." + gethostname() + "." + str(os.getpid())
+ self.restore()
+ self.analysis_cache = {}
+
+ def start(self, parallel_mode=False):
+ self.get_ready()
+ if self.nesting == 0: #pragma: no cover
+ sys.settrace(self.t)
+ if hasattr(threading, 'settrace'):
+ threading.settrace(self.t)
+ self.nesting += 1
+
+ def stop(self):
+ self.nesting -= 1
+ if self.nesting == 0: #pragma: no cover
+ sys.settrace(None)
+ if hasattr(threading, 'settrace'):
+ threading.settrace(None)
+
+ def erase(self):
+ self.get_ready()
+ self.c = {}
+ self.analysis_cache = {}
+ self.cexecuted = {}
+ if self.cache and os.path.exists(self.cache):
+ os.remove(self.cache)
+
+ def exclude(self, re):
+ if self.exclude_re:
+ self.exclude_re += "|"
+ self.exclude_re += "(" + re + ")"
+
+ def begin_recursive(self):
+ self.cstack.append(self.c)
+ self.xstack.append(self.exclude_re)
+
+ def end_recursive(self):
+ self.c = self.cstack.pop()
+ self.exclude_re = self.xstack.pop()
+
+ # save(). Save coverage data to the coverage cache.
+
+ def save(self):
+ if self.usecache and self.cache:
+ self.canonicalize_filenames()
+ cache = open(self.cache, 'wb')
+ import marshal
+ marshal.dump(self.cexecuted, cache)
+ cache.close()
+
+ # restore(). Restore coverage data from the coverage cache (if it exists).
+
+ def restore(self):
+ self.c = {}
+ self.cexecuted = {}
+ assert self.usecache
+ if os.path.exists(self.cache):
+ self.cexecuted = self.restore_file(self.cache)
+
+ def restore_file(self, file_name):
+ try:
+ cache = open(file_name, 'rb')
+ import marshal
+ cexecuted = marshal.load(cache)
+ cache.close()
+ if isinstance(cexecuted, types.DictType):
+ return cexecuted
+ else:
+ return {}
+ except:
+ return {}
+
+ # collect(). Collect data in multiple files produced by parallel mode
+
+ def collect(self):
+ cache_dir, local = os.path.split(self.cache)
+ for f in os.listdir(cache_dir or '.'):
+ if not f.startswith(local):
+ continue
+
+ full_path = os.path.join(cache_dir, f)
+ cexecuted = self.restore_file(full_path)
+ self.merge_data(cexecuted)
+
+ def merge_data(self, new_data):
+ for file_name, file_data in new_data.items():
+ if self.cexecuted.has_key(file_name):
+ self.merge_file_data(self.cexecuted[file_name], file_data)
+ else:
+ self.cexecuted[file_name] = file_data
+
+ def merge_file_data(self, cache_data, new_data):
+ for line_number in new_data.keys():
+ if not cache_data.has_key(line_number):
+ cache_data[line_number] = new_data[line_number]
+
+ def abs_file(self, filename):
+ """ Helper function to turn a filename into an absolute normalized
+ filename.
+ """
+ return os.path.normcase(os.path.abspath(os.path.realpath(filename)))
+
+ def get_zip_data(self, filename):
+ """ Get data from `filename` if it is a zip file path, or return None
+ if it is not.
+ """
+ markers = ['.zip'+os.sep, '.egg'+os.sep]
+ for marker in markers:
+ if marker in filename:
+ parts = filename.split(marker)
+ try:
+ zi = zipimport.zipimporter(parts[0]+marker[:-1])
+ except zipimport.ZipImportError:
+ continue
+ try:
+ data = zi.get_data(parts[1])
+ except IOError:
+ continue
+ return data
+ return None
+
+ # canonical_filename(filename). Return a canonical filename for the
+ # file (that is, an absolute path with no redundant components and
+ # normalized case). See [GDR 2001-12-04b, 3.3].
+
+ def canonical_filename(self, filename):
+ if not self.canonical_filename_cache.has_key(filename):
+ f = filename
+ if os.path.isabs(f) and not os.path.exists(f):
+ if not self.get_zip_data(f):
+ f = os.path.basename(f)
+ if not os.path.isabs(f):
+ for path in [os.curdir] + sys.path:
+ g = os.path.join(path, f)
+ if os.path.exists(g):
+ f = g
+ break
+ cf = self.abs_file(f)
+ self.canonical_filename_cache[filename] = cf
+ return self.canonical_filename_cache[filename]
+
+ # canonicalize_filenames(). Copy results from "c" to "cexecuted",
+ # canonicalizing filenames on the way. Clear the "c" map.
+
+ def canonicalize_filenames(self):
+ for filename, lineno in self.c.keys():
+ if filename == '<string>':
+ # Can't do anything useful with exec'd strings, so skip them.
+ continue
+ f = self.canonical_filename(filename)
+ if not self.cexecuted.has_key(f):
+ self.cexecuted[f] = {}
+ self.cexecuted[f][lineno] = 1
+ self.c = {}
+
+ # morf_filename(morf). Return the filename for a module or file.
+
+ def morf_filename(self, morf):
+ if hasattr(morf, '__file__'):
+ f = morf.__file__
+ else:
+ f = morf
+ return self.canonical_filename(f)
+
+ # analyze_morf(morf). Analyze the module or filename passed as
+ # the argument. If the source code can't be found, raise an error.
+ # Otherwise, return a tuple of (1) the canonical filename of the
+ # source code for the module, (2) a list of lines of statements
+ # in the source code, (3) a list of lines of excluded statements,
+ # and (4), a map of line numbers to multi-line line number ranges, for
+ # statements that cross lines.
+
+ def analyze_morf(self, morf):
+ if self.analysis_cache.has_key(morf):
+ return self.analysis_cache[morf]
+ filename = self.morf_filename(morf)
+ ext = os.path.splitext(filename)[1]
+ source, sourcef = None, None
+ if ext == '.pyc':
+ if not os.path.exists(filename[:-1]):
+ source = self.get_zip_data(filename[:-1])
+ if not source:
+ raise CoverageException(
+ "No source for compiled code '%s'." % filename
+ )
+ filename = filename[:-1]
+ if not source:
+ sourcef = open(filename, 'rU')
+ source = sourcef.read()
+ try:
+ lines, excluded_lines, line_map = self.find_executable_statements(
+ source, exclude=self.exclude_re
+ )
+ except SyntaxError, synerr:
+ raise CoverageException(
+ "Couldn't parse '%s' as Python source: '%s' at line %d" %
+ (filename, synerr.msg, synerr.lineno)
+ )
+ if sourcef:
+ sourcef.close()
+ result = filename, lines, excluded_lines, line_map
+ self.analysis_cache[morf] = result
+ return result
+
+ def first_line_of_tree(self, tree):
+ while True:
+ if len(tree) == 3 and type(tree[2]) == type(1):
+ return tree[2]
+ tree = tree[1]
+
+ def last_line_of_tree(self, tree):
+ while True:
+ if len(tree) == 3 and type(tree[2]) == type(1):
+ return tree[2]
+ tree = tree[-1]
+
+ def find_docstring_pass_pair(self, tree, spots):
+ for i in range(1, len(tree)):
+ if self.is_string_constant(tree[i]) and self.is_pass_stmt(tree[i+1]):
+ first_line = self.first_line_of_tree(tree[i])
+ last_line = self.last_line_of_tree(tree[i+1])
+ self.record_multiline(spots, first_line, last_line)
+
+ def is_string_constant(self, tree):
+ try:
+ return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.expr_stmt
+ except:
+ return False
+
+ def is_pass_stmt(self, tree):
+ try:
+ return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.pass_stmt
+ except:
+ return False
+
+ def record_multiline(self, spots, i, j):
+ for l in range(i, j+1):
+ spots[l] = (i, j)
+
+ def get_suite_spots(self, tree, spots):
+ """ Analyze a parse tree to find suite introducers which span a number
+ of lines.
+ """
+ for i in range(1, len(tree)):
+ if type(tree[i]) == type(()):
+ if tree[i][0] == symbol.suite:
+ # Found a suite, look back for the colon and keyword.
+ lineno_colon = lineno_word = None
+ for j in range(i-1, 0, -1):
+ if tree[j][0] == token.COLON:
+ # Colons are never executed themselves: we want the
+ # line number of the last token before the colon.
+ lineno_colon = self.last_line_of_tree(tree[j-1])
+ elif tree[j][0] == token.NAME:
+ if tree[j][1] == 'elif':
+ # Find the line number of the first non-terminal
+ # after the keyword.
+ t = tree[j+1]
+ while t and token.ISNONTERMINAL(t[0]):
+ t = t[1]
+ if t:
+ lineno_word = t[2]
+ else:
+ lineno_word = tree[j][2]
+ break
+ elif tree[j][0] == symbol.except_clause:
+ # "except" clauses look like:
+ # ('except_clause', ('NAME', 'except', lineno), ...)
+ if tree[j][1][0] == token.NAME:
+ lineno_word = tree[j][1][2]
+ break
+ if lineno_colon and lineno_word:
+ # Found colon and keyword, mark all the lines
+ # between the two with the two line numbers.
+ self.record_multiline(spots, lineno_word, lineno_colon)
+
+ # "pass" statements are tricky: different versions of Python
+ # treat them differently, especially in the common case of a
+ # function with a doc string and a single pass statement.
+ self.find_docstring_pass_pair(tree[i], spots)
+
+ elif tree[i][0] == symbol.simple_stmt:
+ first_line = self.first_line_of_tree(tree[i])
+ last_line = self.last_line_of_tree(tree[i])
+ if first_line != last_line:
+ self.record_multiline(spots, first_line, last_line)
+ self.get_suite_spots(tree[i], spots)
+
+ def find_executable_statements(self, text, exclude=None):
+ # Find lines which match an exclusion pattern.
+ excluded = {}
+ suite_spots = {}
+ if exclude:
+ reExclude = re.compile(exclude)
+ lines = text.split('\n')
+ for i in range(len(lines)):
+ if reExclude.search(lines[i]):
+ excluded[i+1] = 1
+
+ # Parse the code and analyze the parse tree to find out which statements
+ # are multiline, and where suites begin and end.
+ import parser
+ tree = parser.suite(text+'\n\n').totuple(1)
+ self.get_suite_spots(tree, suite_spots)
+ #print "Suite spots:", suite_spots
+
+ # Use the compiler module to parse the text and find the executable
+ # statements. We add newlines to be impervious to final partial lines.
+ statements = {}
+ ast = compiler.parse(text+'\n\n')
+ visitor = StatementFindingAstVisitor(statements, excluded, suite_spots)
+ compiler.walk(ast, visitor, walker=visitor)
+
+ lines = statements.keys()
+ lines.sort()
+ excluded_lines = excluded.keys()
+ excluded_lines.sort()
+ return lines, excluded_lines, suite_spots
+
+ # format_lines(statements, lines). Format a list of line numbers
+ # for printing by coalescing groups of lines as long as the lines
+ # represent consecutive statements. This will coalesce even if
+ # there are gaps between statements, so if statements =
+ # [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then
+ # format_lines will return "1-2, 5-11, 13-14".
+
+ def format_lines(self, statements, lines):
+ pairs = []
+ i = 0
+ j = 0
+ start = None
+ pairs = []
+ while i < len(statements) and j < len(lines):
+ if statements[i] == lines[j]:
+ if start == None:
+ start = lines[j]
+ end = lines[j]
+ j = j + 1
+ elif start:
+ pairs.append((start, end))
+ start = None
+ i = i + 1
+ if start:
+ pairs.append((start, end))
+ def stringify(pair):
+ start, end = pair
+ if start == end:
+ return "%d" % start
+ else:
+ return "%d-%d" % (start, end)
+ ret = string.join(map(stringify, pairs), ", ")
+ return ret
+
+ # Backward compatibility with version 1.
+ def analysis(self, morf):
+ f, s, _, m, mf = self.analysis2(morf)
+ return f, s, m, mf
+
+ def analysis2(self, morf):
+ filename, statements, excluded, line_map = self.analyze_morf(morf)
+ self.canonicalize_filenames()
+ if not self.cexecuted.has_key(filename):
+ self.cexecuted[filename] = {}
+ missing = []
+ for line in statements:
+ lines = line_map.get(line, [line, line])
+ for l in range(lines[0], lines[1]+1):
+ if self.cexecuted[filename].has_key(l):
+ break
+ else:
+ missing.append(line)
+ return (filename, statements, excluded, missing,
+ self.format_lines(statements, missing))
+
+ def relative_filename(self, filename):
+ """ Convert filename to relative filename from self.relative_dir.
+ """
+ return filename.replace(self.relative_dir, "")
+
+ def morf_name(self, morf):
+ """ Return the name of morf as used in report.
+ """
+ if hasattr(morf, '__name__'):
+ return morf.__name__
+ else:
+ return self.relative_filename(os.path.splitext(morf)[0])
+
+ def filter_by_prefix(self, morfs, omit_prefixes):
+ """ Return list of morfs where the morf name does not begin
+ with any one of the omit_prefixes.
+ """
+ filtered_morfs = []
+ for morf in morfs:
+ for prefix in omit_prefixes:
+ if self.morf_name(morf).startswith(prefix):
+ break
+ else:
+ filtered_morfs.append(morf)
+
+ return filtered_morfs
+
+ def morf_name_compare(self, x, y):
+ return cmp(self.morf_name(x), self.morf_name(y))
+
+ def report(self, morfs, show_missing=1, ignore_errors=0, file=None, omit_prefixes=[]):
+ if not isinstance(morfs, types.ListType):
+ morfs = [morfs]
+ # On windows, the shell doesn't expand wildcards. Do it here.
+ globbed = []
+ for morf in morfs:
+ if isinstance(morf, strclass):
+ globbed.extend(glob.glob(morf))
+ else:
+ globbed.append(morf)
+ morfs = globbed
+
+ morfs = self.filter_by_prefix(morfs, omit_prefixes)
+ morfs.sort(self.morf_name_compare)
+
+ max_name = max([5,] + map(len, map(self.morf_name, morfs)))
+ fmt_name = "%%- %ds " % max_name
+ fmt_err = fmt_name + "%s: %s"
+ header = fmt_name % "Name" + " Stmts Exec Cover"
+ fmt_coverage = fmt_name + "% 6d % 6d % 5d%%"
+ if show_missing:
+ header = header + " Missing"
+ fmt_coverage = fmt_coverage + " %s"
+ if not file:
+ file = sys.stdout
+ print >>file, header
+ print >>file, "-" * len(header)
+ total_statements = 0
+ total_executed = 0
+ for morf in morfs:
+ name = self.morf_name(morf)
+ try:
+ _, statements, _, missing, readable = self.analysis2(morf)
+ n = len(statements)
+ m = n - len(missing)
+ if n > 0:
+ pc = 100.0 * m / n
+ else:
+ pc = 100.0
+ args = (name, n, m, pc)
+ if show_missing:
+ args = args + (readable,)
+ print >>file, fmt_coverage % args
+ total_statements = total_statements + n
+ total_executed = total_executed + m
+ except KeyboardInterrupt: #pragma: no cover
+ raise
+ except:
+ if not ignore_errors:
+ typ, msg = sys.exc_info()[:2]
+ print >>file, fmt_err % (name, typ, msg)
+ if len(morfs) > 1:
+ print >>file, "-" * len(header)
+ if total_statements > 0:
+ pc = 100.0 * total_executed / total_statements
+ else:
+ pc = 100.0
+ args = ("TOTAL", total_statements, total_executed, pc)
+ if show_missing:
+ args = args + ("",)
+ print >>file, fmt_coverage % args
+
+ # annotate(morfs, ignore_errors).
+
+ blank_re = re.compile(r"\s*(#|$)")
+ else_re = re.compile(r"\s*else\s*:\s*(#|$)")
+
+ def annotate(self, morfs, directory=None, ignore_errors=0, omit_prefixes=[]):
+ morfs = self.filter_by_prefix(morfs, omit_prefixes)
+ for morf in morfs:
+ try:
+ filename, statements, excluded, missing, _ = self.analysis2(morf)
+ self.annotate_file(filename, statements, excluded, missing, directory)
+ except KeyboardInterrupt:
+ raise
+ except:
+ if not ignore_errors:
+ raise
+
+ def annotate_file(self, filename, statements, excluded, missing, directory=None):
+ source = open(filename, 'r')
+ if directory:
+ dest_file = os.path.join(directory,
+ os.path.basename(filename)
+ + ',cover')
+ else:
+ dest_file = filename + ',cover'
+ dest = open(dest_file, 'w')
+ lineno = 0
+ i = 0
+ j = 0
+ covered = 1
+ while 1:
+ line = source.readline()
+ if line == '':
+ break
+ lineno = lineno + 1
+ while i < len(statements) and statements[i] < lineno:
+ i = i + 1
+ while j < len(missing) and missing[j] < lineno:
+ j = j + 1
+ if i < len(statements) and statements[i] == lineno:
+ covered = j >= len(missing) or missing[j] > lineno
+ if self.blank_re.match(line):
+ dest.write(' ')
+ elif self.else_re.match(line):
+ # Special logic for lines containing only 'else:'.
+ # See [GDR 2001-12-04b, 3.2].
+ if i >= len(statements) and j >= len(missing):
+ dest.write('! ')
+ elif i >= len(statements) or j >= len(missing):
+ dest.write('> ')
+ elif statements[i] == missing[j]:
+ dest.write('! ')
+ else:
+ dest.write('> ')
+ elif lineno in excluded:
+ dest.write('- ')
+ elif covered:
+ dest.write('> ')
+ else:
+ dest.write('! ')
+ dest.write(line)
+ source.close()
+ dest.close()
+
+# Singleton object.
+the_coverage = coverage()
+
+# Module functions call methods in the singleton object.
+def use_cache(*args, **kw):
+ return the_coverage.use_cache(*args, **kw)
+
+def start(*args, **kw):
+ return the_coverage.start(*args, **kw)
+
+def stop(*args, **kw):
+ return the_coverage.stop(*args, **kw)
+
+def erase(*args, **kw):
+ return the_coverage.erase(*args, **kw)
+
+def begin_recursive(*args, **kw):
+ return the_coverage.begin_recursive(*args, **kw)
+
+def end_recursive(*args, **kw):
+ return the_coverage.end_recursive(*args, **kw)
+
+def exclude(*args, **kw):
+ return the_coverage.exclude(*args, **kw)
+
+def analysis(*args, **kw):
+ return the_coverage.analysis(*args, **kw)
+
+def analysis2(*args, **kw):
+ return the_coverage.analysis2(*args, **kw)
+
+def report(*args, **kw):
+ return the_coverage.report(*args, **kw)
+
+def annotate(*args, **kw):
+ return the_coverage.annotate(*args, **kw)
+
+def annotate_file(*args, **kw):
+ return the_coverage.annotate_file(*args, **kw)
+
+# Save coverage data when Python exits. (The atexit module wasn't
+# introduced until Python 2.0, so use sys.exitfunc when it's not
+# available.)
+try:
+ import atexit
+ atexit.register(the_coverage.save)
+except ImportError:
+ sys.exitfunc = the_coverage.save
+
+def main():
+ the_coverage.command_line(sys.argv[1:])
+
+# Command-line interface.
+if __name__ == '__main__':
+ main()
+
+
+# A. REFERENCES
+#
+# [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees;
+# Ravenbrook Limited; 2001-12-04;
+# <http://www.nedbatchelder.com/code/modules/rees-coverage.html>.
+#
+# [GDR 2001-12-04b] "Statement coverage for Python: design and
+# analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04;
+# <http://www.nedbatchelder.com/code/modules/rees-design.html>.
+#
+# [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)";
+# Guide van Rossum; 2001-07-20;
+# <http://www.python.org/doc/2.1.1/ref/ref.html>.
+#
+# [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum;
+# 2001-07-20; <http://www.python.org/doc/2.1.1/lib/lib.html>.
+#
+#
+# B. DOCUMENT HISTORY
+#
+# 2001-12-04 GDR Created.
+#
+# 2001-12-06 GDR Added command-line interface and source code
+# annotation.
+#
+# 2001-12-09 GDR Moved design and interface to separate documents.
+#
+# 2001-12-10 GDR Open cache file as binary on Windows. Allow
+# simultaneous -e and -x, or -a and -r.
+#
+# 2001-12-12 GDR Added command-line help. Cache analysis so that it
+# only needs to be done once when you specify -a and -r.
+#
+# 2001-12-13 GDR Improved speed while recording. Portable between
+# Python 1.5.2 and 2.1.1.
+#
+# 2002-01-03 GDR Module-level functions work correctly.
+#
+# 2002-01-07 GDR Update sys.path when running a file with the -x option,
+# so that it matches the value the program would get if it were run on
+# its own.
+#
+# 2004-12-12 NMB Significant code changes.
+# - Finding executable statements has been rewritten so that docstrings and
+# other quirks of Python execution aren't mistakenly identified as missing
+# lines.
+# - Lines can be excluded from consideration, even entire suites of lines.
+# - The filesystem cache of covered lines can be disabled programmatically.
+# - Modernized the code.
+#
+# 2004-12-14 NMB Minor tweaks. Return 'analysis' to its original behavior
+# and add 'analysis2'. Add a global for 'annotate', and factor it, adding
+# 'annotate_file'.
+#
+# 2004-12-31 NMB Allow for keyword arguments in the module global functions.
+# Thanks, Allen.
+#
+# 2005-12-02 NMB Call threading.settrace so that all threads are measured.
+# Thanks Martin Fuzzey. Add a file argument to report so that reports can be
+# captured to a different destination.
+#
+# 2005-12-03 NMB coverage.py can now measure itself.
+#
+# 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames,
+# and sorting and omitting files to report on.
+#
+# 2006-07-23 NMB Applied Joseph Tate's patch for function decorators.
+#
+# 2006-08-21 NMB Applied Sigve Tjora and Mark van der Wal's fixes for argument
+# handling.
+#
+# 2006-08-22 NMB Applied Geoff Bache's parallel mode patch.
+#
+# 2006-08-23 NMB Refactorings to improve testability. Fixes to command-line
+# logic for parallel mode and collect.
+#
+# 2006-08-25 NMB "#pragma: nocover" is excluded by default.
+#
+# 2006-09-10 NMB Properly ignore docstrings and other constant expressions that
+# appear in the middle of a function, a problem reported by Tim Leslie.
+# Minor changes to avoid lint warnings.
+#
+# 2006-09-17 NMB coverage.erase() shouldn't clobber the exclude regex.
+# Change how parallel mode is invoked, and fix erase() so that it erases the
+# cache when called programmatically.
+#
+# 2007-07-21 NMB In reports, ignore code executed from strings, since we can't
+# do anything useful with it anyway.
+# Better file handling on Linux, thanks Guillaume Chazarain.
+# Better shell support on Windows, thanks Noel O'Boyle.
+# Python 2.2 support maintained, thanks Catherine Proulx.
+#
+# 2007-07-22 NMB Python 2.5 now fully supported. The method of dealing with
+# multi-line statements is now less sensitive to the exact line that Python
+# reports during execution. Pass statements are handled specially so that their
+# disappearance during execution won't throw off the measurement.
+#
+# 2007-07-23 NMB Now Python 2.5 is *really* fully supported: the body of the
+# new with statement is counted as executable.
+#
+# 2007-07-29 NMB Better packaging.
+#
+# 2007-09-30 NMB Don't try to predict whether a file is Python source based on
+# the extension. Extensionless files are often Pythons scripts. Instead, simply
+# parse the file and catch the syntax errors. Hat tip to Ben Finney.
+#
+# 2008-05-25 NMB Open files in rU mode to avoid line ending craziness.
+# Thanks, Edward Loper.
+#
+# 2008-09-14 NMB Add support for finding source files in eggs.
+# Don't check for morf's being instances of ModuleType, instead use duck typing
+# so that pseudo-modules can participate. Thanks, Imri Goldberg.
+# Use os.realpath as part of the fixing of filenames so that symlinks won't
+# confuse things. Thanks, Patrick Mezard.
+#
+#
+# C. COPYRIGHT AND LICENCE
+#
+# Copyright 2001 Gareth Rees. All rights reserved.
+# Copyright 2004-2008 Ned Batchelder. 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
+# HOLDERS AND 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.
+#
+# $Id: coverage.py 100 2008-10-12 12:08:22Z nedbat $
diff --git a/tests/root/_templates/layout.html b/tests/root/_templates/layout.html
index 1f4688e6..e8920025 100644
--- a/tests/root/_templates/layout.html
+++ b/tests/root/_templates/layout.html
@@ -1,4 +1,5 @@
{% extends "!layout.html" %}
{% block extrahead %}
<meta name="hc" content="{{ hckey }}" />
+{{ super() }}
{% endblock %}
diff --git a/tests/root/autodoc.txt b/tests/root/autodoc.txt
index 2b57e0e9..c718feb4 100644
--- a/tests/root/autodoc.txt
+++ b/tests/root/autodoc.txt
@@ -1,7 +1,28 @@
Autodoc tests
=============
+Just testing a few autodoc possibilities...
+
+.. automodule:: util
+
.. automodule:: test_autodoc
:members:
.. autofunction:: function
+
+.. autoclass:: Class
+ :inherited-members:
+
+ Additional content.
+
+.. autoclass:: Outer
+ :members: Inner
+
+.. autoattribute:: Class.docattr
+
+.. autoexception:: CustomEx
+ :members: f
+
+.. autoclass:: CustomDict
+ :show-inheritance:
+ :members:
diff --git a/tests/root/autosummary.txt b/tests/root/autosummary.txt
new file mode 100644
index 00000000..edf75e32
--- /dev/null
+++ b/tests/root/autosummary.txt
@@ -0,0 +1,7 @@
+Autosummary test
+================
+
+.. autosummary::
+ :toctree: generated
+
+ sphinx.application.TemplateBridge
diff --git a/tests/root/conf.py b/tests/root/conf.py
index 12951d03..fd82be7d 100644
--- a/tests/root/conf.py
+++ b/tests/root/conf.py
@@ -1,183 +1,71 @@
# -*- coding: utf-8 -*-
-#
-# Sphinx Tests documentation build configuration file, created by
-# sphinx-quickstart on Wed Jun 4 23:49:58 2008.
-#
-# This file is execfile()d with the current directory set to its containing dir.
-#
-# The contents of this file are pickled, so don't put values in the namespace
-# that aren't pickleable (module imports are okay, they're removed automatically).
-#
-# All configuration values have a default value; values that are commented out
-# serve to show the default value.
import sys, os
-# If your extensions are in another directory, add it here. If the directory
-# is relative to the documentation root, use os.path.abspath to make it
-# absolute, like shown here.
sys.path.append(os.path.abspath('.'))
-# General configuration
-# ---------------------
-
-# Add any Sphinx extension module names here, as strings. They can be extensions
-# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['ext', 'sphinx.ext.autodoc', 'sphinx.ext.jsmath',
- 'sphinx.ext.coverage', 'sphinx.ext.todo']
+ 'sphinx.ext.coverage', 'sphinx.ext.todo',
+ 'sphinx.ext.autosummary']
+
jsmath_path = 'dummy.js'
-# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
-# The suffix of source filenames.
-source_suffix = '.txt'
-
-# The master toctree document.
master_doc = 'contents'
+source_suffix = '.txt'
-# General substitutions.
project = 'Sphinx <Tests>'
copyright = '2008, Georg Brandl & Team'
-
-# The default replacements for |version| and |release|, also used in various
-# other places throughout the built documents.
-#
-# The short X.Y version.
-version = '0.4'
-# The full version, including alpha/beta/rc tags.
-release = '0.4alpha1'
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
+# If this is changed, remember to update the versionchanges!
+version = '0.6'
+release = '0.6alpha1'
today_fmt = '%B %d, %Y'
-
-# List of documents that shouldn't be included in the build.
#unused_docs = []
-
-# List of directories, relative to source directories, that shouldn't be searched
-# for source files.
exclude_trees = ['_build']
-
keep_warnings = True
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
+rst_epilog = '.. |subst| replace:: global substitution'
-# Options for HTML output
-# -----------------------
+html_theme = 'testtheme'
+html_theme_path = ['.']
+html_theme_options = {'testopt': 'testoverride'}
-# The style sheet to use for HTML and HTML Help pages. A file of that name
-# must exist either in Sphinx' static/ path, or in one of the custom paths
-# given in html_static_path.
html_style = 'default.css'
-
-# The name for this set of Sphinx documents. If None, it defaults to
-# "<project> v<release> documentation".
-#html_title = None
-
-# A shorter title for the navigation bar. Default is the same as html_title.
-#html_short_title = None
-
-# The name of an image file (within the static path) to place at the top of
-# the sidebar.
-#html_logo = None
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-#html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-#html_use_modindex = True
-
-# If false, no index is generated.
-#html_use_index = True
-
-# If true, the reST sources are included in the HTML build as _sources/<name>.
-#html_copy_source = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it. The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = ''
-
html_context = {'hckey': 'hcval'}
-# Output file base name for HTML help builder.
htmlhelp_basename = 'SphinxTestsdoc'
-
-# Options for LaTeX output
-# ------------------------
-
-# The paper size ('letter' or 'a4').
-#latex_paper_size = 'letter'
-
-# The font size ('10pt', '11pt' or '12pt').
-#latex_font_size = '10pt'
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, document class [howto/manual]).
latex_documents = [
('contents', 'SphinxTests.tex', 'Sphinx Tests Documentation',
'Georg Brandl', 'manual'),
]
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# Additional stuff for the LaTeX preamble.
-#latex_preamble = ''
-
-# Documents to append as an appendix to all manuals.
-#latex_appendices = []
-
-# If false, no module index is generated.
-#latex_use_modindex = True
+latex_additional_files = ['svgimg.svg']
value_from_conf_py = 84
coverage_c_path = ['special/*.h']
coverage_c_regexes = {'cfunction': r'^PyAPI_FUNC\(.*\)\s+([^_][\w_]+)'}
+autosummary_generate = ['autosummary']
+
+# modify tags from conf.py
+tags.add('confpytag')
+
+from sphinx import addnodes
+
+def userdesc_parse(env, sig, signode):
+ x, y = sig.split(':')
+ signode += addnodes.desc_name(x, x)
+ signode += addnodes.desc_parameterlist()
+ signode[-1] += addnodes.desc_parameter(y, y)
+ return x
def setup(app):
app.add_config_value('value_from_conf_py', 42, False)
+ app.add_description_unit('userdesc', 'userdescrole', '%s (userdesc)',
+ userdesc_parse)
diff --git a/tests/root/contents.txt b/tests/root/contents.txt
index 61646f62..24d790a5 100644
--- a/tests/root/contents.txt
+++ b/tests/root/contents.txt
@@ -9,14 +9,19 @@ Contents:
.. toctree::
:maxdepth: 2
+ :numbered:
images
subdir/images
+ subdir/includes
includes
markup
desc
math
autodoc
+ autosummary
+
+ Python <http://python.org/>
Indices and tables
==================
diff --git a/tests/root/desc.txt b/tests/root/desc.txt
index beb470b6..d6915dc2 100644
--- a/tests/root/desc.txt
+++ b/tests/root/desc.txt
@@ -58,3 +58,14 @@ Testing references
==================
Referencing :class:`mod.Cls` or :Class:`mod.Cls` should be the same.
+
+
+User markup
+===========
+
+.. userdesc:: myobj:parameter
+
+ Description of userdesc.
+
+
+Referencing :userdescrole:`myobj`.
diff --git a/tests/root/images.txt b/tests/root/images.txt
index be868dfe..bd64d573 100644
--- a/tests/root/images.txt
+++ b/tests/root/images.txt
@@ -23,3 +23,6 @@ Sphinx image handling
.. an image with subdir and unspecified extension
.. image:: subdir/simg.*
+
+.. an SVG image (for HTML at least)
+.. image:: svgimg.*
diff --git a/tests/root/includes.txt b/tests/root/includes.txt
index ad507fc6..44e33af0 100644
--- a/tests/root/includes.txt
+++ b/tests/root/includes.txt
@@ -14,3 +14,33 @@ Test file and literal inclusion
:encoding: latin-1
.. include:: wrongenc.inc
:encoding: latin-1
+
+Literalinclude options
+======================
+
+.. highlight:: text
+
+.. cssclass:: inc-pyobj1
+.. literalinclude:: literal.inc
+ :pyobject: Foo
+
+.. cssclass:: inc-pyobj2
+.. literalinclude:: literal.inc
+ :pyobject: Bar.baz
+
+.. cssclass:: inc-lines
+.. literalinclude:: literal.inc
+ :lines: 6-7,9
+
+.. cssclass:: inc-startend
+.. literalinclude:: literal.inc
+ :start-after: coding: utf-8
+ :end-before: class Foo
+
+
+Testing downloadable files
+==========================
+
+Download :download:`img.png` here.
+Download :download:`this <subdir/img.png>` there.
+Don't download :download:`this <nonexisting.png>`.
diff --git a/tests/root/literal.inc b/tests/root/literal.inc
index a4ce93d2..d5b9890c 100644
--- a/tests/root/literal.inc
+++ b/tests/root/literal.inc
@@ -2,3 +2,12 @@
# -*- coding: utf-8 -*-
foo = u"Including Unicode characters: üöä"
+
+class Foo:
+ pass
+
+class Bar:
+ def baz():
+ pass
+
+def bar(): pass
diff --git a/tests/root/markup.txt b/tests/root/markup.txt
index 454762e3..52d407e2 100644
--- a/tests/root/markup.txt
+++ b/tests/root/markup.txt
@@ -11,6 +11,8 @@ Testing various markup
:author: Me
:keywords: docs, sphinx
+A |subst|.
+
.. _label:
::
@@ -82,13 +84,13 @@ Tables
Version markup
--------------
-.. versionadded:: 0.5
+.. versionadded:: 0.6
Some funny **stuff**.
-.. versionchanged:: 0.5
+.. versionchanged:: 0.6
Even more funny stuff.
-.. deprecated:: 0.4
+.. deprecated:: 0.6
Boring stuff.
@@ -104,6 +106,16 @@ Reference lookup: [Ref1]_ (defined in another file).
`Google <http://www.google.com>`_
For everything.
+.. hlist::
+ :columns: 4
+
+ * This
+ * is
+ * a horizontal
+ * list
+ * with several
+ * items
+
.. rubric:: Side note
This is a side note.
@@ -140,6 +152,7 @@ Option list:
try2_stmt: "try" ":" `suite`
: "finally" ":" `suite`
+Test :abbr:`abbr (abbreviation)` and another :abbr:`abbr (abbreviation)`.
Index markup
------------
@@ -164,6 +177,26 @@ Invalid index markup...
Testing öäü...
+Only directive
+--------------
+
+.. only:: html
+
+ In HTML.
+
+.. only:: latex
+
+ In LaTeX.
+
+.. only:: html or latex
+
+ In both.
+
+.. only:: confpytag and (testtag or nonexisting_tag)
+
+ Always present, because set through conf.py/command line.
+
+
.. rubric:: Footnotes
.. [#] Like footnotes.
diff --git a/tests/root/rimg.png b/tests/root/rimg.png
new file mode 100644
index 00000000..1081dc14
--- /dev/null
+++ b/tests/root/rimg.png
Binary files differ
diff --git a/tests/root/special/code.py b/tests/root/special/code.py
new file mode 100644
index 00000000..70c48d2e
--- /dev/null
+++ b/tests/root/special/code.py
@@ -0,0 +1,2 @@
+print "line 1"
+print "line 2"
diff --git a/tests/root/subdir/images.txt b/tests/root/subdir/images.txt
index 33adf5b5..f2adf88d 100644
--- a/tests/root/subdir/images.txt
+++ b/tests/root/subdir/images.txt
@@ -2,3 +2,5 @@ Image including source in subdir
================================
.. image:: img.*
+
+.. image:: /rimg.png
diff --git a/tests/root/subdir/includes.txt b/tests/root/subdir/includes.txt
new file mode 100644
index 00000000..3e1ae0d1
--- /dev/null
+++ b/tests/root/subdir/includes.txt
@@ -0,0 +1,12 @@
+Including in subdir
+===================
+
+.. absolute filename
+.. literalinclude:: /special/code.py
+ :lines: 1
+
+.. relative filename
+.. literalinclude:: ../special/code.py
+ :lines: 2
+
+Absolute :download:`/img.png` download.
diff --git a/tests/root/svgimg.pdf b/tests/root/svgimg.pdf
new file mode 100644
index 00000000..cacbd855
--- /dev/null
+++ b/tests/root/svgimg.pdf
Binary files differ
diff --git a/tests/root/svgimg.svg b/tests/root/svgimg.svg
new file mode 100644
index 00000000..10e035b6
--- /dev/null
+++ b/tests/root/svgimg.svg
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ height="60"
+ width="60"
+ _SVGFile__filename="oldscale/apps/warning.svg"
+ version="1.0"
+ y="0"
+ x="0"
+ id="svg1"
+ sodipodi:version="0.32"
+ inkscape:version="0.41"
+ sodipodi:docname="exclamation.svg"
+ sodipodi:docbase="/home/danny/work/icons/primary/scalable/actions">
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0000000"
+ inkscape:pageshadow="2"
+ inkscape:zoom="7.5136000"
+ inkscape:cx="42.825186"
+ inkscape:cy="24.316071"
+ inkscape:window-width="1020"
+ inkscape:window-height="691"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:current-layer="svg1" />
+ <defs
+ id="defs3">
+ <linearGradient
+ id="linearGradient1160">
+ <stop
+ style="stop-color: #000000;stop-opacity: 1.0;"
+ id="stop1161"
+ offset="0" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ id="stop1162"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ xlink:href="#linearGradient1160"
+ id="linearGradient1163" />
+ </defs>
+ <metadata
+ id="metadata12">
+ <RDF
+ id="RDF13">
+ <Work
+ about=""
+ id="Work14">
+ <title
+ id="title15">Part of the Flat Icon Collection (Thu Aug 26 14:31:40 2004)</title>
+ <description
+ id="description17" />
+ <subject
+ id="subject18">
+ <Bag
+ id="Bag19">
+ <li
+ id="li20" />
+ </Bag>
+ </subject>
+ <publisher
+ id="publisher21">
+ <Agent
+ about=""
+ id="Agent22">
+ <title
+ id="title23" />
+ </Agent>
+ </publisher>
+ <creator
+ id="creator24">
+ <Agent
+ about=""
+ id="Agent25">
+ <title
+ id="title26">Danny Allen</title>
+ </Agent>
+ </creator>
+ <rights
+ id="rights28">
+ <Agent
+ about=""
+ id="Agent29">
+ <title
+ id="title30">Danny Allen</title>
+ </Agent>
+ </rights>
+ <date
+ id="date32" />
+ <format
+ id="format33">image/svg+xml</format>
+ <type
+ id="type35"
+ resource="http://purl.org/dc/dcmitype/StillImage" />
+ <license
+ id="license36"
+ resource="http://creativecommons.org/licenses/LGPL/2.1/">
+ <date
+ id="date37" />
+ </license>
+ <language
+ id="language38">en</language>
+ </Work>
+ </RDF>
+ <rdf:RDF
+ id="RDF40">
+ <cc:Work
+ rdf:about=""
+ id="Work41">
+ <dc:format
+ id="format42">image/svg+xml</dc:format>
+ <dc:type
+ id="type44"
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="g2099">
+ <path
+ style="color:#000000;fill:none;fill-opacity:1.0000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.1250000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ d="M 55.311891,51.920745 L 4.6880989,51.920744 L 29.999995,8.0792542 L 55.311891,51.920745 z "
+ id="path1724" />
+ <path
+ style="color:#000000;fill:#ffe940;fill-opacity:1.0000000;fill-rule:evenodd;stroke:#000000;stroke-width:3.1250010;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ d="M 55.311891,51.920745 L 4.6880989,51.920744 L 29.999995,8.0792542 L 55.311891,51.920745 z "
+ id="path1722" />
+ <path
+ style="font-size:12.000000;font-weight:900;fill:none;fill-opacity:1.0000000;stroke:#ffffff;stroke-width:8.1250000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 34.944960,10.779626 L 34.944960,33.186510 C 34.944960,34.752415 34.501979,36.081368 33.616007,37.173380 C 32.750636,38.265402 31.545298,38.811408 29.999995,38.811408 C 28.475302,38.811408 27.269965,38.265402 26.383993,37.173380 C 25.498020,36.060767 25.055030,34.731804 25.055030,33.186510 L 25.055030,10.779626 C 25.055030,9.1931155 25.498020,7.8641562 26.383993,6.7927462 C 27.269965,5.7007332 28.475302,5.1547262 29.999995,5.1547262 C 31.009593,5.1547262 31.885265,5.4019740 32.627010,5.8964706 C 33.389356,6.3909681 33.966274,7.0709005 34.357752,7.9362696 C 34.749221,8.7810349 34.944960,9.7288200 34.944960,10.779626 z "
+ id="path1099" />
+ <path
+ style="font-size:12.000000;font-weight:900;fill:#e71c02;fill-opacity:1.0000000;stroke:none;stroke-width:3.1249981;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1.0000000"
+ d="M 29.999995,3.5986440 C 28.102272,3.5986440 26.318514,4.3848272 25.156245,5.8173940 C 24.028906,7.1806889 23.499995,8.9087770 23.499995,10.786144 L 23.499995,33.192394 C 23.499995,35.036302 24.050685,36.772771 25.156245,38.161144 C 26.318514,39.593721 28.102273,40.379893 29.999995,40.379894 C 31.913354,40.379894 33.697195,39.576736 34.843745,38.129894 C 35.959941,36.754118 36.499995,35.052976 36.499995,33.192394 L 36.499995,10.786144 C 36.499995,9.5413010 36.276626,8.3551469 35.781245,7.2861440 C 35.278844,6.1755772 34.477762,5.2531440 33.468745,4.5986440 C 32.454761,3.9226545 31.264694,3.5986439 29.999995,3.5986440 z "
+ id="path835"
+ sodipodi:nodetypes="cccccccccccc" />
+ <path
+ style="color:#000000;fill:none;fill-opacity:1.0000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:5.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ d="M 36.506243,49.901522 C 36.506243,53.492972 33.591442,56.407773 29.999991,56.407773 C 26.408541,56.407773 23.493739,53.492972 23.493739,49.901522 C 23.493739,46.310071 26.408541,43.395270 29.999991,43.395270 C 33.591442,43.395270 36.506243,46.310071 36.506243,49.901522 z "
+ id="path1727" />
+ <path
+ style="color:#000000;fill:#e71c02;fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:3.1250000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ d="M 36.506243,49.901522 C 36.506243,53.492972 33.591442,56.407773 29.999991,56.407773 C 26.408541,56.407773 23.493739,53.492972 23.493739,49.901522 C 23.493739,46.310071 26.408541,43.395270 29.999991,43.395270 C 33.591442,43.395270 36.506243,46.310071 36.506243,49.901522 z "
+ id="path1725" />
+ </g>
+</svg>
diff --git a/tests/root/testtheme/layout.html b/tests/root/testtheme/layout.html
new file mode 100644
index 00000000..81372be0
--- /dev/null
+++ b/tests/root/testtheme/layout.html
@@ -0,0 +1,5 @@
+{% extends "basic/layout.html" %}
+{% block extrahead %}
+<meta name="testopt" content="{{ theme_testopt }}" />
+{{ super() }}
+{% endblock %}
diff --git a/tests/root/testtheme/static/staticimg.png b/tests/root/testtheme/static/staticimg.png
new file mode 100644
index 00000000..1081dc14
--- /dev/null
+++ b/tests/root/testtheme/static/staticimg.png
Binary files differ
diff --git a/tests/root/testtheme/static/statictmpl.html_t b/tests/root/testtheme/static/statictmpl.html_t
new file mode 100644
index 00000000..4ab292b4
--- /dev/null
+++ b/tests/root/testtheme/static/statictmpl.html_t
@@ -0,0 +1,2 @@
+<!-- testing static templates -->
+<html><project>{{ project|e }}</project></html>
diff --git a/tests/root/testtheme/theme.conf b/tests/root/testtheme/theme.conf
new file mode 100644
index 00000000..a8776737
--- /dev/null
+++ b/tests/root/testtheme/theme.conf
@@ -0,0 +1,7 @@
+[theme]
+inherit = basic
+stylesheet = default.css
+pygments_style = emacs
+
+[options]
+testopt = optdefault
diff --git a/tests/root/ziptheme.zip b/tests/root/ziptheme.zip
new file mode 100644
index 00000000..8a246ed9
--- /dev/null
+++ b/tests/root/ziptheme.zip
Binary files differ
diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py
index 752c0725..8e011438 100644
--- a/tests/test_autodoc.py
+++ b/tests/test_autodoc.py
@@ -3,7 +3,7 @@
test_autodoc
~~~~~~~~~~~~
- Test the autodoc extension. This tests mainly the RstGenerator; the auto
+ Test the autodoc extension. This tests mainly the Documenters; the auto
directives are tested in a test source file translated by test_build.
:copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
@@ -14,16 +14,18 @@ from util import *
from docutils.statemachine import ViewList
-from sphinx.ext.autodoc import RstGenerator, cut_lines, between
+from sphinx.ext.autodoc import AutoDirective, Documenter, add_documenter, \
+ ModuleLevelDocumenter, FunctionDocumenter, cut_lines, between, ALL
def setup_module():
- global app, lid, options, gen
+ global app, lid, options, directive
app = TestApp()
app.builder.env.app = app
app.connect('autodoc-process-docstring', process_docstring)
app.connect('autodoc-process-signature', process_signature)
+ app.connect('autodoc-skip-member', skip_member)
options = Struct(
inherited_members = False,
@@ -33,27 +35,27 @@ def setup_module():
synopsis = '',
platform = '',
deprecated = False,
+ members = [],
+ member_order = 'alphabetic',
+ exclude_members = set(),
)
- gen = TestGenerator(options, app)
+ directive = Struct(
+ env = app.builder.env,
+ genopt = options,
+ result = ViewList(),
+ warn = warnfunc,
+ filename_set = set(),
+ )
def teardown_module():
app.cleanup()
-class TestGenerator(RstGenerator):
- """Generator that handles warnings without a reporter."""
-
- def __init__(self, options, app):
- self.options = options
- self.env = app.builder.env
- self.lineno = 42
- self.filename_set = set()
- self.warnings = []
- self.result = ViewList()
+_warnings = []
- def warn(self, msg):
- self.warnings.append(msg)
+def warnfunc(msg):
+ _warnings.append(msg)
processed_docstrings = []
@@ -71,62 +73,73 @@ def process_signature(app, what, name, obj, options, args, retann):
return '42', None
-def test_resolve_name():
- # for modules
- assert gen.resolve_name('module', 'test_autodoc') == \
- ('test_autodoc', 'test_autodoc', [], None, None)
- assert gen.resolve_name('module', 'test.test_autodoc') == \
- ('test.test_autodoc', 'test.test_autodoc', [], None, None)
+def skip_member(app, what, name, obj, skip, options):
+ if name.startswith('_'):
+ return True
+ if name == 'skipmeth':
+ return True
- assert gen.resolve_name('module', 'test(arg)') == \
- ('test', 'test', [], None, None)
- assert 'ignoring signature arguments' in gen.warnings[0]
- del gen.warnings[:]
+
+def test_parse_name():
+ def verify(objtype, name, result):
+ inst = AutoDirective._registry[objtype](directive, name)
+ assert inst.parse_name()
+ assert (inst.modname, inst.objpath, inst.args, inst.retann) == result
+
+ # for modules
+ verify('module', 'test_autodoc', ('test_autodoc', [], None, None))
+ verify('module', 'test.test_autodoc', ('test.test_autodoc', [], None, None))
+ verify('module', 'test(arg)', ('test', [], 'arg', None))
+ assert 'signature arguments' in _warnings[0]
+ del _warnings[:]
# for functions/classes
- assert gen.resolve_name('function', 'util.raises') == \
- ('util.raises', 'util', ['raises'], None, None)
- assert gen.resolve_name('function', 'util.raises(exc) -> None') == \
- ('util.raises', 'util', ['raises'], 'exc', ' -> None')
- gen.env.autodoc_current_module = 'util'
- assert gen.resolve_name('function', 'raises') == \
- ('raises', 'util', ['raises'], None, None)
- gen.env.autodoc_current_module = None
- gen.env.currmodule = 'util'
- assert gen.resolve_name('function', 'raises') == \
- ('raises', 'util', ['raises'], None, None)
- assert gen.resolve_name('class', 'TestApp') == \
- ('TestApp', 'util', ['TestApp'], None, None)
+ verify('function', 'util.raises', ('util', ['raises'], None, None))
+ verify('function', 'util.raises(exc) -> None',
+ ('util', ['raises'], 'exc', 'None'))
+ directive.env.autodoc_current_module = 'util'
+ verify('function', 'raises', ('util', ['raises'], None, None))
+ directive.env.autodoc_current_module = None
+ directive.env.currmodule = 'util'
+ verify('function', 'raises', ('util', ['raises'], None, None))
+ verify('class', 'TestApp', ('util', ['TestApp'], None, None))
# for members
- gen.env.currmodule = 'foo'
- assert gen.resolve_name('method', 'util.TestApp.cleanup') == \
- ('util.TestApp.cleanup', 'util', ['TestApp', 'cleanup'], None, None)
- gen.env.currmodule = 'util'
- gen.env.currclass = 'Foo'
- gen.env.autodoc_current_class = 'TestApp'
- assert gen.resolve_name('method', 'cleanup') == \
- ('cleanup', 'util', ['TestApp', 'cleanup'], None, None)
- assert gen.resolve_name('method', 'TestApp.cleanup') == \
- ('TestApp.cleanup', 'util', ['TestApp', 'cleanup'], None, None)
+ directive.env.currmodule = 'foo'
+ verify('method', 'util.TestApp.cleanup',
+ ('util', ['TestApp', 'cleanup'], None, None))
+ directive.env.currmodule = 'util'
+ directive.env.currclass = 'Foo'
+ directive.env.autodoc_current_class = 'TestApp'
+ verify('method', 'cleanup', ('util', ['TestApp', 'cleanup'], None, None))
+ verify('method', 'TestApp.cleanup',
+ ('util', ['TestApp', 'cleanup'], None, None))
# and clean up
- gen.env.currmodule = None
- gen.env.currclass = None
- gen.env.autodoc_current_class = None
+ directive.env.currmodule = None
+ directive.env.currclass = None
+ directive.env.autodoc_current_class = None
def test_format_signature():
+ def formatsig(objtype, name, obj, args, retann):
+ inst = AutoDirective._registry[objtype](directive, name)
+ inst.fullname = name
+ inst.doc_as_attr = False # for class objtype
+ inst.object = obj
+ inst.args = args
+ inst.retann = retann
+ return inst.format_signature()
+
# no signatures for modules
- assert gen.format_signature('module', 'test', None, None, None) == ''
+ assert formatsig('module', 'test', None, None, None) == ''
# test for functions
def f(a, b, c=1, **d):
pass
- assert gen.format_signature('function', 'f', f, None, None) == '(a, b, c=1, **d)'
- assert gen.format_signature('function', 'f', f, 'a, b, c, d', None) == \
- '(a, b, c, d)'
- assert gen.format_signature('function', 'f', f, None, ' -> None') == \
+ assert formatsig('function', 'f', f, None, None) == '(a, b, c=1, **d)'
+ assert formatsig('function', 'f', f, 'a, b, c, d', None) == '(a, b, c, d)'
+ assert formatsig('function', 'f', f, None, 'None') == \
'(a, b, c=1, **d) -> None'
# test for classes
@@ -136,15 +149,15 @@ def test_format_signature():
pass
# no signature for classes without __init__
for C in (D, E):
- assert gen.format_signature('class', 'D', C, None, None) == ''
+ assert formatsig('class', 'D', C, None, None) == ''
class F:
def __init__(self, a, b=None):
pass
class G(F, object):
pass
for C in (F, G):
- assert gen.format_signature('class', 'C', C, None, None) == '(a, b=None)'
- assert gen.format_signature('class', 'C', D, 'a, b', ' -> X') == '(a, b) -> X'
+ assert formatsig('class', 'C', C, None, None) == '(a, b=None)'
+ assert formatsig('class', 'C', D, 'a, b', 'X') == '(a, b) -> X'
# test for methods
class H:
@@ -152,26 +165,29 @@ def test_format_signature():
pass
def foo2(b, *c):
pass
- assert gen.format_signature('method', 'H.foo', H.foo1, None, None) == '(b, *c)'
- assert gen.format_signature('method', 'H.foo', H.foo1, 'a', None) == '(a)'
- assert gen.format_signature('method', 'H.foo', H.foo2, None, None) == '(b, *c)'
+ assert formatsig('method', 'H.foo', H.foo1, None, None) == '(b, *c)'
+ assert formatsig('method', 'H.foo', H.foo1, 'a', None) == '(a)'
+ assert formatsig('method', 'H.foo', H.foo2, None, None) == '(b, *c)'
# test exception handling
- raises(RuntimeError, gen.format_signature, 'function', 'int', int, None, None)
+ raises(TypeError, formatsig, 'function', 'int', int, None, None)
# test processing by event handler
- assert gen.format_signature('method', 'bar', H.foo1, None, None) == '42'
+ assert formatsig('method', 'bar', H.foo1, None, None) == '42'
def test_get_doc():
- def getdocl(*args):
- # strip the empty line at the end
- return list(gen.get_doc(*args))[:-1]
+ def getdocl(objtype, obj, encoding=None):
+ inst = AutoDirective._registry[objtype](directive, 'tmp')
+ inst.object = obj
+ ds = inst.get_doc(encoding)
+ # for testing purposes, concat them and strip the empty line at the end
+ return sum(ds, [])[:-1]
# objects without docstring
def f():
pass
- assert getdocl('function', 'f', f) == []
+ assert getdocl('function', f) == []
# standard function, diverse docstring styles...
def f():
@@ -181,7 +197,7 @@ def test_get_doc():
Docstring
"""
for func in (f, g):
- assert getdocl('function', 'f', func) == ['Docstring']
+ assert getdocl('function', func) == ['Docstring']
# first line vs. other lines indentation
def f():
@@ -190,29 +206,29 @@ def test_get_doc():
Other
lines
"""
- assert getdocl('function', 'f', f) == ['First line', '', 'Other', ' lines']
+ assert getdocl('function', f) == ['First line', '', 'Other', ' lines']
# charset guessing (this module is encoded in utf-8)
def f():
"""Döcstring"""
- assert getdocl('function', 'f', f) == [u'Döcstring']
+ assert getdocl('function', f) == [u'Döcstring']
# already-unicode docstrings must be taken literally
def f():
u"""Döcstring"""
- assert getdocl('function', 'f', f) == [u'Döcstring']
+ assert getdocl('function', f) == [u'Döcstring']
# class docstring: depends on config value which one is taken
class C:
"""Class docstring"""
def __init__(self):
"""Init docstring"""
- gen.env.config.autoclass_content = 'class'
- assert getdocl('class', 'C', C) == ['Class docstring']
- gen.env.config.autoclass_content = 'init'
- assert getdocl('class', 'C', C) == ['Init docstring']
- gen.env.config.autoclass_content = 'both'
- assert getdocl('class', 'C', C) == ['Class docstring', '', 'Init docstring']
+ directive.env.config.autoclass_content = 'class'
+ assert getdocl('class', C) == ['Class docstring']
+ directive.env.config.autoclass_content = 'init'
+ assert getdocl('class', C) == ['Init docstring']
+ directive.env.config.autoclass_content = 'both'
+ assert getdocl('class', C) == ['Class docstring', '', 'Init docstring']
class D:
"""Class docstring"""
@@ -224,26 +240,33 @@ def test_get_doc():
"""
# Indentation is normalized for 'both'
- assert getdocl('class', 'D', D) == ['Class docstring', '', 'Init docstring',
- '', 'Other', ' lines']
+ assert getdocl('class', D) == ['Class docstring', '', 'Init docstring',
+ '', 'Other', ' lines']
+
+
+def test_docstring_processing():
+ def process(objtype, name, obj):
+ inst = AutoDirective._registry[objtype](directive, name)
+ inst.object = obj
+ inst.fullname = name
+ return list(inst.process_doc(inst.get_doc()))
class E:
def __init__(self):
"""Init docstring"""
# docstring processing by event handler
- assert getdocl('class', 'bar', E) == ['Init docstring', '', '42']
+ assert process('class', 'bar', E) == ['Init docstring', '', '42', '']
-
-def test_docstring_processing_functions():
- lid = app.connect('autodoc-process-docstring', cut_lines(1, 1, ['function']))
+ lid = app.connect('autodoc-process-docstring',
+ cut_lines(1, 1, ['function']))
def f():
"""
first line
second line
third line
"""
- assert list(gen.get_doc('function', 'f', f)) == ['second line', '']
+ assert process('function', 'f', f) == ['second line', '']
app.disconnect(lid)
lid = app.connect('autodoc-process-docstring', between('---', ['function']))
@@ -255,118 +278,173 @@ def test_docstring_processing_functions():
---
third line
"""
- assert list(gen.get_doc('function', 'f', f)) == ['second line', '']
+ assert process('function', 'f', f) == ['second line', '']
app.disconnect(lid)
+def test_new_documenter():
+ class MyDocumenter(ModuleLevelDocumenter):
+ objtype = 'integer'
+ directivetype = 'data'
+ priority = 100
+
+ @classmethod
+ def can_document_member(cls, member, membername, isattr, parent):
+ return isinstance(member, int)
+
+ def document_members(self, all_members=False):
+ return
+
+ add_documenter(MyDocumenter)
+
+ def assert_result_contains(item, objtype, name, **kw):
+ inst = AutoDirective._registry[objtype](directive, name)
+ inst.generate(**kw)
+ #print '\n'.join(directive.result)
+ assert len(_warnings) == 0, _warnings
+ assert item in directive.result
+ del directive.result[:]
+
+ options.members = ['integer']
+ assert_result_contains('.. data:: integer', 'module', 'test_autodoc')
+
+
def test_generate():
- def assert_warns(warn_str, *args):
- gen.generate(*args)
- assert len(gen.result) == 0, gen.result
- assert len(gen.warnings) == 1, gen.warnings
- assert warn_str in gen.warnings[0], gen.warnings
- del gen.warnings[:]
-
- def assert_works(*args):
- gen.generate(*args)
- assert gen.result
- assert len(gen.warnings) == 0, gen.warnings
- del gen.result[:]
-
- def assert_processes(items, *args):
+ def assert_warns(warn_str, objtype, name, **kw):
+ inst = AutoDirective._registry[objtype](directive, name)
+ inst.generate(**kw)
+ assert len(directive.result) == 0, directive.result
+ assert len(_warnings) == 1, _warnings
+ assert warn_str in _warnings[0], _warnings
+ del _warnings[:]
+
+ def assert_works(objtype, name, **kw):
+ inst = AutoDirective._registry[objtype](directive, name)
+ inst.generate(**kw)
+ assert directive.result
+ assert len(_warnings) == 0, _warnings
+ del directive.result[:]
+
+ def assert_processes(items, objtype, name, **kw):
del processed_docstrings[:]
del processed_signatures[:]
- assert_works(*args)
- assert set(processed_docstrings) | set(processed_signatures) == set(items)
+ assert_works(objtype, name, **kw)
+ assert set(processed_docstrings) | set(processed_signatures) == \
+ set(items)
- def assert_result_contains(item, *args):
- gen.generate(*args)
- print '\n'.join(gen.result)
- assert len(gen.warnings) == 0, gen.warnings
- assert item in gen.result
- del gen.result[:]
+ def assert_result_contains(item, objtype, name, **kw):
+ inst = AutoDirective._registry[objtype](directive, name)
+ inst.generate(**kw)
+ #print '\n'.join(directive.result)
+ assert len(_warnings) == 0, _warnings
+ assert item in directive.result
+ del directive.result[:]
+
+ options.members = []
# no module found?
assert_warns("import for autodocumenting 'foobar'",
- 'function', 'foobar', None, None)
+ 'function', 'foobar', more_content=None)
# importing
assert_warns("import/find module 'test_foobar'",
- 'module', 'test_foobar', None, None)
+ 'module', 'test_foobar', more_content=None)
# attributes missing
assert_warns("import/find function 'util.foobar'",
- 'function', 'util.foobar', None, None)
+ 'function', 'util.foobar', more_content=None)
# test auto and given content mixing
- gen.env.currmodule = 'test_autodoc'
- assert_result_contains(' Function.', 'method', 'Class.meth', [], None)
+ directive.env.currmodule = 'test_autodoc'
+ assert_result_contains(' Function.', 'method', 'Class.meth')
add_content = ViewList()
add_content.append('Content.', '', 0)
- assert_result_contains(' Function.', 'method', 'Class.meth', [], add_content)
- assert_result_contains(' Content.', 'method', 'Class.meth', [], add_content)
+ assert_result_contains(' Function.', 'method',
+ 'Class.meth', more_content=add_content)
+ assert_result_contains(' Content.', 'method',
+ 'Class.meth', more_content=add_content)
# test check_module
- gen.generate('function', 'raises', None, None, check_module=True)
- assert len(gen.result) == 0
+ inst = FunctionDocumenter(directive, 'raises')
+ inst.generate(check_module=True)
+ assert len(directive.result) == 0
# assert that exceptions can be documented
- assert_works('exception', 'test_autodoc.CustomEx', ['__all__'], None)
- assert_works('exception', 'test_autodoc.CustomEx', [], None)
+ assert_works('exception', 'test_autodoc.CustomEx', all_members=True)
+ assert_works('exception', 'test_autodoc.CustomEx')
# test diverse inclusion settings for members
- should = [('class', 'Class')]
- assert_processes(should, 'class', 'Class', [], None)
- should.extend([('method', 'Class.meth')])
- assert_processes(should, 'class', 'Class', ['meth'], None)
- should.extend([('attribute', 'Class.prop')])
- assert_processes(should, 'class', 'Class', ['__all__'], None)
+ should = [('class', 'test_autodoc.Class')]
+ assert_processes(should, 'class', 'Class')
+ should.extend([('method', 'test_autodoc.Class.meth')])
+ options.members = ['meth']
+ options.exclude_members = set(['excludemeth'])
+ assert_processes(should, 'class', 'Class')
+ should.extend([('attribute', 'test_autodoc.Class.prop'),
+ ('attribute', 'test_autodoc.Class.attr'),
+ ('attribute', 'test_autodoc.Class.docattr'),
+ ('attribute', 'test_autodoc.Class.udocattr')])
+ options.members = ALL
+ assert_processes(should, 'class', 'Class')
options.undoc_members = True
- should.append(('method', 'Class.undocmeth'))
- assert_processes(should, 'class', 'Class', ['__all__'], None)
+ should.append(('method', 'test_autodoc.Class.undocmeth'))
+ assert_processes(should, 'class', 'Class')
options.inherited_members = True
- should.append(('method', 'Class.inheritedmeth'))
- assert_processes(should, 'class', 'Class', ['__all__'], None)
+ should.append(('method', 'test_autodoc.Class.inheritedmeth'))
+ assert_processes(should, 'class', 'Class')
+ options.members = []
# test module flags
- assert_result_contains('.. module:: test_autodoc', 'module',
- 'test_autodoc', [], None)
+ assert_result_contains('.. module:: test_autodoc', 'module', 'test_autodoc')
options.synopsis = 'Synopsis'
- assert_result_contains(' :synopsis: Synopsis', 'module', 'test_autodoc', [], None)
+ assert_result_contains(' :synopsis: Synopsis', 'module', 'test_autodoc')
options.deprecated = True
- assert_result_contains(' :deprecated:', 'module', 'test_autodoc', [], None)
+ assert_result_contains(' :deprecated:', 'module', 'test_autodoc')
options.platform = 'Platform'
- assert_result_contains(' :platform: Platform', 'module', 'test_autodoc', [], None)
+ assert_result_contains(' :platform: Platform', 'module', 'test_autodoc')
# test if __all__ is respected for modules
- assert_result_contains('.. class:: Class', 'module', 'test_autodoc',
- ['__all__'], None)
+ options.members = ALL
+ assert_result_contains('.. class:: Class', 'module', 'test_autodoc')
try:
- assert_result_contains('.. exception:: CustomEx', 'module', 'test_autodoc',
- ['__all__'], None)
+ assert_result_contains('.. exception:: CustomEx',
+ 'module', 'test_autodoc')
except AssertionError:
pass
else:
assert False, 'documented CustomEx which is not in __all__'
# test noindex flag
+ options.members = []
options.noindex = True
- assert_result_contains(' :noindex:', 'module', 'test_autodoc', [], None)
- assert_result_contains(' :noindex:', 'class', 'Base', [], None)
+ assert_result_contains(' :noindex:', 'module', 'test_autodoc')
+ assert_result_contains(' :noindex:', 'class', 'Base')
# okay, now let's get serious about mixing Python and C signature stuff
assert_result_contains('.. class:: CustomDict', 'class', 'CustomDict',
- ['__all__'], None)
+ all_members=True)
+
+ # test inner class handling
+ assert_processes([('class', 'test_autodoc.Outer'),
+ ('class', 'test_autodoc.Outer.Inner'),
+ ('method', 'test_autodoc.Outer.Inner.meth')],
+ 'class', 'Outer', all_members=True)
+
+ # test generation for C modules (which have no source file)
+ directive.env.currmodule = 'time'
+ assert_processes([('function', 'time.asctime')], 'function', 'asctime')
+ assert_processes([('function', 'time.asctime')], 'function', 'asctime')
# --- generate fodder ------------
__all__ = ['Class']
+integer = 1
+
class CustomEx(Exception):
"""My custom exception."""
def f(self):
"""Exception method."""
-
class Base(object):
def inheritedmeth(self):
"""Inherited function."""
@@ -380,9 +458,28 @@ class Class(Base):
def undocmeth(self):
pass
- @property
+ def skipmeth(self):
+ """Method that should be skipped."""
+
+ def excludemeth(self):
+ """Method that should be excluded."""
+
+ # should not be documented
+ skipattr = 'foo'
+
+ #: should be documented -- süß
+ attr = 'bar'
+
def prop(self):
"""Property."""
+ # stay 2.4 compatible (docstring!)
+ prop = property(prop, doc="Property.")
+
+ docattr = 'baz'
+ """should likewise be documented -- süß"""
+
+ udocattr = 'quux'
+ u"""should be documented as well - süß"""
class CustomDict(dict):
"""Docstring."""
@@ -392,3 +489,16 @@ def function(foo, *args, **kwds):
Return spam.
"""
pass
+
+
+class Outer(object):
+ """Foo"""
+
+ class Inner(object):
+ """Foo"""
+
+ def meth(self):
+ """Foo"""
+
+ # should be documented as an alias
+ factory = dict
diff --git a/tests/test_build.py b/tests/test_build.py
index 888063a9..7b33dc09 100644
--- a/tests/test_build.py
+++ b/tests/test_build.py
@@ -10,6 +10,7 @@
"""
import os
+import re
import sys
import difflib
import htmlentitydefs
@@ -19,29 +20,41 @@ from subprocess import Popen, PIPE
from util import *
from etree13 import ElementTree as ET
-from sphinx.builder import StandaloneHTMLBuilder, LaTeXBuilder
-from sphinx.latexwriter import LaTeXTranslator
+try:
+ import pygments
+except ImportError:
+ pygments = None
+
+from sphinx.builders.html import StandaloneHTMLBuilder
+from sphinx.builders.latex import LaTeXBuilder
+from sphinx.writers.latex import LaTeXTranslator
+
+
+def teardown_module():
+ (test_root / '_build').rmtree(True)
html_warnfile = StringIO()
latex_warnfile = StringIO()
ENV_WARNINGS = """\
-WARNING: %(root)s/images.txt:9: Image file not readable: foo.png
-WARNING: %(root)s/images.txt:23: Nonlocal image URI found: http://www.python.org/logo.png
-WARNING: %(root)s/includes.txt:: (WARNING/2) Encoding 'utf-8' used for reading included \
-file u'wrongenc.inc' seems to be wrong, try giving an :encoding: option
+%(root)s/images.txt:9: WARNING: image file not readable: foo.png
+%(root)s/images.txt:23: WARNING: nonlocal image URI found: \
+http://www.python.org/logo.png
+%(root)s/includes.txt:: (WARNING/2) Encoding 'utf-8' used for reading \
+included file u'wrongenc.inc' seems to be wrong, try giving an :encoding: option
+%(root)s/includes.txt:56: WARNING: download file not readable: nonexisting.png
"""
HTML_WARNINGS = ENV_WARNINGS + """\
-WARNING: %(root)s/images.txt:: no matching candidate for image URI u'foo.*'
-WARNING: %(root)s/markup.txt:: invalid index entry u''
-WARNING: %(root)s/markup.txt:: invalid pair index entry u''
-WARNING: %(root)s/markup.txt:: invalid pair index entry u'keyword; '
+%(root)s/images.txt:20: WARNING: no matching candidate for image URI u'foo.*'
+%(root)s/markup.txt:: WARNING: invalid index entry u''
+%(root)s/markup.txt:: WARNING: invalid pair index entry u''
+%(root)s/markup.txt:: WARNING: invalid pair index entry u'keyword; '
"""
LATEX_WARNINGS = ENV_WARNINGS + """\
-WARNING: None:: no matching candidate for image URI u'foo.*'
+None:None: WARNING: no matching candidate for image URI u'foo.*'
WARNING: invalid pair index entry u''
"""
@@ -50,18 +63,27 @@ HTML_XPATH = {
".//img[@src='_images/img.png']": '',
".//img[@src='_images/img1.png']": '',
".//img[@src='_images/simg.png']": '',
+ ".//object[@data='_images/svgimg.svg']": '',
+ ".//embed[@src='_images/svgimg.svg']": '',
},
'subdir/images.html': {
".//img[@src='../_images/img1.png']": '',
+ ".//img[@src='../_images/rimg.png']": '',
+ },
+ 'subdir/includes.html': {
+ ".//pre/span": 'line 1',
+ ".//pre/span": 'line 2',
+ ".//a[@href='../_downloads/img.png']": '',
},
'includes.html': {
- ".//pre/span[@class='s']": u'üöä',
".//pre": u'Max Strauß',
+ ".//a[@href='_downloads/img.png']": '',
+ ".//a[@href='_downloads/img1.png']": '',
},
'autodoc.html': {
".//dt[@id='test_autodoc.Class']": '',
- ".//dt[@id='test_autodoc.function']/em": '**kwds',
- ".//dd": 'Return spam.',
+ ".//dt[@id='test_autodoc.function']/em": r'\*\*kwds',
+ ".//dd": r'Return spam\.',
},
'markup.html': {
".//meta[@name='author'][@content='Me']": '',
@@ -69,23 +91,48 @@ HTML_XPATH = {
".//a[@href='contents.html#ref1']": '',
".//div[@id='label']": '',
".//span[@class='option']": '--help',
+ ".//p": 'A global substitution.',
+ ".//p": 'In HTML.',
+ ".//p": 'In both.',
+ ".//p": 'Always present',
},
'desc.html': {
".//dt[@id='mod.Cls.meth1']": '',
".//dt[@id='errmod.Error']": '',
".//a[@href='#mod.Cls']": '',
+ ".//dl[@class='userdesc']": '',
+ ".//dt[@id='userdescrole-myobj']": '',
+ ".//a[@href='#userdescrole-myobj']": '',
},
'contents.html': {
".//meta[@name='hc'][@content='hcval']": '',
- #".//td[@class='label']": '[Ref1]', # docutils 0.5 only
+ ".//meta[@name='testopt'][@content='testoverride']": '',
+ #".//td[@class='label']": r'\[Ref1\]', # docutils 0.5 only
".//td[@class='label']": '',
".//li[@class='toctree-l1']/a": 'Testing various markup',
".//li[@class='toctree-l2']/a": 'Admonitions',
".//title": 'Sphinx <Tests>',
".//div[@class='footer']": 'Georg Brandl & Team',
+ ".//a[@href='http://python.org/']": '',
+ },
+ '_static/statictmpl.html': {
+ ".//project": 'Sphinx <Tests>',
},
}
+if pygments:
+ HTML_XPATH['includes.html'].update({
+ ".//pre/span[@class='s']": u'üöä',
+ ".//div[@class='inc-pyobj1 highlight-text']/div/pre":
+ r'^class Foo:\n pass\n\s*$',
+ ".//div[@class='inc-pyobj2 highlight-text']/div/pre":
+ r'^ def baz\(\):\n pass\n\s*$',
+ ".//div[@class='inc-lines highlight-text']/div/pre":
+ r'^class Foo:\n pass\nclass Bar:\n$',
+ ".//div[@class='inc-startend highlight-text']/div/pre":
+ ur'^foo = u"Including Unicode characters: üöä"\n$',
+ })
+
class NslessParser(ET.XMLParser):
"""XMLParser that throws away namespaces in tag names."""
@@ -101,7 +148,27 @@ class NslessParser(ET.XMLParser):
return name
-@with_app(buildername='html', warning=html_warnfile, cleanenv=True)
+def check_xpath(etree, fname, path, check):
+ nodes = list(etree.findall(path))
+ assert nodes != [], ('did not find any node matching xpath '
+ '%r in file %s' % (path, fname))
+ if hasattr(check, '__call__'):
+ check(nodes)
+ elif not check:
+ # only check for node presence
+ pass
+ else:
+ rex = re.compile(check)
+ for node in nodes:
+ if node.text and rex.search(node.text):
+ break
+ else:
+ assert False, ('%r not found in any node matching '
+ 'path %s in %s: %r' % (check, path, fname,
+ [node.text for node in nodes]))
+
+@gen_with_app(buildername='html', warning=html_warnfile, cleanenv=True,
+ tags=['testtag'])
def test_html(app):
app.builder.build_all()
html_warnings = html_warnfile.getvalue().replace(os.sep, '/')
@@ -114,18 +181,8 @@ def test_html(app):
parser = NslessParser()
parser.entity.update(htmlentitydefs.entitydefs)
etree = ET.parse(os.path.join(app.outdir, fname), parser)
- for path, text in paths.iteritems():
- nodes = list(etree.findall(path))
- assert nodes != []
- if not text:
- # only check for node presence
- continue
- for node in nodes:
- if node.text and text in node.text:
- break
- else:
- assert False, ('%r not found in any node matching '
- 'path %s in %s' % (text, path, fname))
+ for path, check in paths.iteritems():
+ yield check_xpath, etree, fname, path, check
@with_app(buildername='latex', warning=latex_warnfile, cleanenv=True)
@@ -137,6 +194,8 @@ def test_latex(app):
assert latex_warnings == latex_warnings_exp, 'Warnings don\'t match:\n' + \
'\n'.join(difflib.ndiff(latex_warnings_exp.splitlines(),
latex_warnings.splitlines()))
+ # file from latex_additional_files
+ assert (app.outdir / 'svgimg.svg').isfile()
# only run latex if all needed packages are there
def kpsetest(filename):
@@ -155,12 +214,15 @@ def test_latex(app):
return True
if kpsetest('article.sty') is None:
- print >>sys.stderr, 'info: not running latex, it doesn\'t seem to be installed'
+ print >>sys.stderr, \
+ 'info: not running latex, it doesn\'t seem to be installed'
return
- for filename in ['fancyhdr.sty', 'fancybox.sty', 'titlesec.sty', 'amsmath.sty',
- 'framed.sty', 'color.sty', 'fancyvrb.sty', 'threeparttable.sty']:
+ for filename in ['fancyhdr.sty', 'fancybox.sty', 'titlesec.sty',
+ 'amsmath.sty', 'framed.sty', 'color.sty', 'fancyvrb.sty',
+ 'threeparttable.sty']:
if not kpsetest(filename):
- print >>sys.stderr, 'info: not running latex, the %s package doesn\'t ' \
+ print >>sys.stderr, \
+ 'info: not running latex, the %s package doesn\'t ' \
'seem to be installed' % filename
return
@@ -169,8 +231,8 @@ def test_latex(app):
os.chdir(app.outdir)
try:
try:
- p = Popen(['pdflatex', '--interaction=nonstopmode', 'SphinxTests.tex'],
- stdout=PIPE, stderr=PIPE)
+ p = Popen(['pdflatex', '--interaction=nonstopmode',
+ 'SphinxTests.tex'], stdout=PIPE, stderr=PIPE)
except OSError, err:
pass # most likely pdflatex was not found
else:
@@ -184,14 +246,26 @@ def test_latex(app):
# just let the remaining ones run for now
-@with_app(buildername='linkcheck', cleanenv=True)
+@with_app(buildername='pickle')
+def test_pickle(app):
+ app.builder.build_all()
+
+@with_app(buildername='linkcheck')
def test_linkcheck(app):
app.builder.build_all()
-@with_app(buildername='text', cleanenv=True)
+@with_app(buildername='text')
def test_text(app):
app.builder.build_all()
+@with_app(buildername='htmlhelp')
+def test_htmlhelp(app):
+ app.builder.build_all()
+
+@with_app(buildername='qthelp')
+def test_qthelp(app):
+ app.builder.build_all()
+
@with_app(buildername='changes', cleanenv=True)
def test_changes(app):
app.builder.build_all()
diff --git a/tests/test_config.py b/tests/test_config.py
index bc5ab8a0..b3aa4eea 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -15,7 +15,8 @@ from util import *
from sphinx.application import ExtensionError
-@with_app(confoverrides={'master_doc': 'master', 'nonexisting_value': 'True'})
+@with_app(confoverrides={'master_doc': 'master', 'nonexisting_value': 'True',
+ 'latex_elements.docclass': 'scrartcl'})
def test_core_config(app):
cfg = app.config
@@ -26,6 +27,7 @@ def test_core_config(app):
# overrides
assert cfg.master_doc == 'master'
+ assert cfg.latex_elements['docclass'] == 'scrartcl'
# simple default values
assert 'exclude_dirs' not in cfg.__dict__
@@ -34,7 +36,7 @@ def test_core_config(app):
# complex default values
assert 'html_title' not in cfg.__dict__
- assert cfg.html_title == 'Sphinx <Tests> v0.4alpha1 documentation'
+ assert cfg.html_title == 'Sphinx <Tests> v0.6alpha1 documentation'
# complex default values mustn't raise
for valuename in cfg.config_values:
diff --git a/tests/test_env.py b/tests/test_env.py
index 390c6999..a06656d6 100644
--- a/tests/test_env.py
+++ b/tests/test_env.py
@@ -12,7 +12,8 @@
from util import *
from sphinx.environment import BuildEnvironment
-from sphinx.builder import StandaloneHTMLBuilder, LaTeXBuilder
+from sphinx.builders.html import StandaloneHTMLBuilder
+from sphinx.builders.latex import LaTeXBuilder
app = env = None
warnings = []
@@ -21,14 +22,14 @@ def setup_module():
global app, env
app = TestApp(srcdir='(temp)')
env = BuildEnvironment(app.srcdir, app.doctreedir, app.config)
- env.set_warnfunc(warnings.append)
+ env.set_warnfunc(lambda *args: warnings.append(args))
def teardown_module():
app.cleanup()
def warning_emitted(file, text):
for warning in warnings:
- if file+':' in warning and text in warning:
+ if len(warning) == 2 and file+':' in warning[1] and text in warning[0]:
return True
return False
@@ -36,8 +37,7 @@ def warning_emitted(file, text):
# afford to not run update() in the setup but in its own test
def test_first_update():
- it = env.update(app.config, app.srcdir, app.doctreedir, app)
- msg = it.next()
+ msg, num, it = env.update(app.config, app.srcdir, app.doctreedir, app)
assert msg.endswith('%d added, 0 changed, 0 removed' % len(env.found_docs))
docnames = set()
for docname in it: # the generator does all the work
@@ -45,44 +45,51 @@ def test_first_update():
assert docnames == env.found_docs == set(env.all_docs)
def test_images():
- assert warning_emitted('images.txt', 'Image file not readable: foo.png')
- assert warning_emitted('images.txt', 'Nonlocal image URI found: '
+ assert warning_emitted('images.txt', 'image file not readable: foo.png')
+ assert warning_emitted('images.txt', 'nonlocal image URI found: '
'http://www.python.org/logo.png')
tree = env.get_doctree('images')
app._warning.reset()
htmlbuilder = StandaloneHTMLBuilder(app, env)
htmlbuilder.post_process_images(tree)
- assert "no matching candidate for image URI u'foo.*'" in app._warning.content[-1]
- assert set(htmlbuilder.images.keys()) == set(['subdir/img.png', 'img.png',
- 'subdir/simg.png'])
- assert set(htmlbuilder.images.values()) == set(['img.png', 'img1.png',
- 'simg.png'])
+ assert "no matching candidate for image URI u'foo.*'" in \
+ app._warning.content[-1]
+ assert set(htmlbuilder.images.keys()) == \
+ set(['subdir/img.png', 'img.png', 'subdir/simg.png', 'svgimg.svg'])
+ assert set(htmlbuilder.images.values()) == \
+ set(['img.png', 'img1.png', 'simg.png', 'svgimg.svg'])
app._warning.reset()
latexbuilder = LaTeXBuilder(app, env)
latexbuilder.post_process_images(tree)
- assert "no matching candidate for image URI u'foo.*'" in app._warning.content[-1]
- assert set(latexbuilder.images.keys()) == set(['subdir/img.png', 'subdir/simg.png',
- 'img.png', 'img.pdf'])
- assert set(latexbuilder.images.values()) == set(['img.pdf', 'img.png',
- 'img1.png', 'simg.png'])
+ assert "no matching candidate for image URI u'foo.*'" in \
+ app._warning.content[-1]
+ assert set(latexbuilder.images.keys()) == \
+ set(['subdir/img.png', 'subdir/simg.png', 'img.png', 'img.pdf',
+ 'svgimg.pdf'])
+ assert set(latexbuilder.images.values()) == \
+ set(['img.pdf', 'img.png', 'img1.png', 'simg.png', 'svgimg.pdf'])
def test_second_update():
# delete, add and "edit" (change saved mtime) some files and update again
env.all_docs['contents'] = 0
root = path(app.srcdir)
- (root / 'images.txt').unlink()
+ # important: using "autodoc" because it is the last one to be included in
+ # the contents.txt toctree; otherwise section numbers would shift
+ (root / 'autodoc.txt').unlink()
(root / 'new.txt').write_text('New file\n========\n')
- it = env.update(app.config, app.srcdir, app.doctreedir, app)
- msg = it.next()
- assert '1 added, 1 changed, 1 removed' in msg
+ msg, num, it = env.update(app.config, app.srcdir, app.doctreedir, app)
+ assert '1 added, 3 changed, 1 removed' in msg
docnames = set()
for docname in it:
docnames.add(docname)
- assert docnames == set(['contents', 'new'])
- assert 'images' not in env.all_docs
- assert 'images' not in env.found_docs
+ # "includes" and "images" are in there because they contain references
+ # to nonexisting downloadable or image files, which are given another
+ # chance to exist
+ assert docnames == set(['contents', 'new', 'includes', 'images'])
+ assert 'autodoc' not in env.all_docs
+ assert 'autodoc' not in env.found_docs
def test_object_inventory():
refs = env.descrefs
diff --git a/tests/test_highlighting.py b/tests/test_highlighting.py
new file mode 100644
index 00000000..ea1f25f1
--- /dev/null
+++ b/tests/test_highlighting.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+"""
+ test_highlighting
+ ~~~~~~~~~~~~~~~~~
+
+ Test the Pygments highlighting bridge.
+
+ :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+from util import *
+
+try:
+ import pygments
+except ImportError:
+ from nose.plugins.skip import SkipTest
+ raise SkipTest('pygments not available')
+
+from pygments.lexer import RegexLexer
+from pygments.token import Text, Name
+from pygments.formatters.html import HtmlFormatter
+
+from sphinx.highlighting import PygmentsBridge
+
+
+class MyLexer(RegexLexer):
+ name = 'testlexer'
+
+ tokens = {
+ 'root': [
+ ('a', Name),
+ ('b', Text),
+ ],
+ }
+
+
+class MyFormatter(HtmlFormatter):
+ def format(self, tokensource, outfile):
+ outfile.write('test')
+
+
+class ComplainOnUnhighlighted(PygmentsBridge):
+ def unhighlighted(self, source):
+ raise AssertionError("should highlight %r" % source)
+
+
+@with_app()
+def test_add_lexer(app):
+ app.add_lexer('test', MyLexer())
+
+ bridge = PygmentsBridge('html')
+ ret = bridge.highlight_block('ab', 'test')
+ assert '<span class="n">a</span>b' in ret
+
+def test_detect_interactive():
+ bridge = ComplainOnUnhighlighted('html')
+ blocks = [
+ """
+ >>> testing()
+ True
+ """,
+ ]
+ for block in blocks:
+ ret = bridge.highlight_block(block.lstrip(), 'python')
+ assert ret.startswith("<div class=\"highlight\">")
+
+def test_set_formatter():
+ PygmentsBridge.html_formatter = MyFormatter
+ try:
+ bridge = PygmentsBridge('html')
+ ret = bridge.highlight_block('foo', 'python')
+ assert ret == 'test'
+ finally:
+ PygmentsBridge.html_formatter = HtmlFormatter
diff --git a/tests/test_markup.py b/tests/test_markup.py
index 169cdca9..28ec17f5 100644
--- a/tests/test_markup.py
+++ b/tests/test_markup.py
@@ -17,11 +17,13 @@ from docutils import frontend, utils, nodes
from docutils.parsers import rst
from sphinx import addnodes
-from sphinx.htmlwriter import HTMLWriter, SmartyPantsHTMLTranslator
-from sphinx.latexwriter import LaTeXWriter, LaTeXTranslator
+from sphinx.util import texescape
+from sphinx.writers.html import HTMLWriter, SmartyPantsHTMLTranslator
+from sphinx.writers.latex import LaTeXWriter, LaTeXTranslator
def setup_module():
global app, settings, parser
+ texescape.init() # otherwise done by the latex builder
app = TestApp(cleanenv=True)
optparser = frontend.OptionParser(
components=(rst.Parser, HTMLWriter, LaTeXWriter))
@@ -79,38 +81,39 @@ def test_inline():
# correct interpretation of code with whitespace
_html = ('<p><tt class="docutils literal"><span class="pre">'
'code</span>&nbsp;&nbsp; <span class="pre">sample</span></tt></p>')
- verify('``code sample``', _html, '\\code{code sample}')
- verify(':samp:`code sample`', _html, '\\samp{code sample}')
+ yield verify, '``code sample``', _html, '\\code{code sample}'
+ yield verify, ':samp:`code sample`', _html, '\\samp{code sample}'
# interpolation of braces in samp and file roles (HTML only)
- verify(':samp:`a{b}c`',
+ yield (verify, ':samp:`a{b}c`',
'<p><tt class="docutils literal"><span class="pre">a</span>'
- '<em><span class="pre">b</span></em><span class="pre">c</span></tt></p>',
+ '<em><span class="pre">b</span></em>'
+ '<span class="pre">c</span></tt></p>',
'\\samp{abc}')
# interpolation of arrows in menuselection
- verify(':menuselection:`a --> b`',
+ yield (verify, ':menuselection:`a --> b`',
u'<p><em>a \N{TRIANGULAR BULLET} b</em></p>',
'\\emph{a \\(\\rightarrow\\) b}')
# non-interpolation of dashes in option role
- verify_re(':option:`--with-option`',
- '<p><em( class="xref")?>--with-option</em></p>$',
- r'\\emph{\\texttt{-{-}with-option}}$')
+ yield (verify_re, ':option:`--with-option`',
+ '<p><em( class="xref")?>--with-option</em></p>$',
+ r'\\emph{\\texttt{-{-}with-option}}$')
# verify smarty-pants quotes
- verify('"John"', '<p>&#8220;John&#8221;</p>', "``John''")
+ yield verify, '"John"', '<p>&#8220;John&#8221;</p>', "``John''"
# ... but not in literal text
- verify('``"John"``',
+ yield (verify, '``"John"``',
'<p><tt class="docutils literal"><span class="pre">'
'&quot;John&quot;</span></tt></p>',
'\\code{"John"}')
def test_latex_escaping():
# correct escaping in normal mode
- verify(u'Γ\\\\∞$', None, ur'\(\Gamma\)\textbackslash{}\(\infty\)\$')
+ yield verify, u'Γ\\\\∞$', None, ur'\(\Gamma\)\textbackslash{}\(\infty\)\$'
# in verbatim code fragments
- verify(u'::\n\n @Γ\\∞$[]', None,
+ yield (verify, u'::\n\n @Γ\\∞$[]', None,
u'\\begin{Verbatim}[commandchars=@\\[\\]]\n'
u'@PYGZat[]@(@Gamma@)\\@(@infty@)@$@PYGZlb[]@PYGZrb[]\n'
u'\\end{Verbatim}')
diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py
index 4c2d0f3e..ae001eb6 100644
--- a/tests/test_quickstart.py
+++ b/tests/test_quickstart.py
@@ -70,6 +70,7 @@ def test_do_prompt():
assert d['k5'] == 'no'
raises(AssertionError, qs.do_prompt, d, 'k6', 'Q6', validator=qs.boolean)
+
@with_tempdir
def test_quickstart_defaults(tempdir):
answers = {
@@ -86,29 +87,31 @@ def test_quickstart_defaults(tempdir):
ns = {}
execfile(conffile, ns)
assert ns['extensions'] == []
- assert ns['templates_path'] == ['.templates']
+ assert ns['templates_path'] == ['_templates']
assert ns['source_suffix'] == '.rst'
assert ns['master_doc'] == 'index'
assert ns['project'] == 'Sphinx Test'
assert ns['copyright'] == '%s, Georg Brandl' % time.strftime('%Y')
assert ns['version'] == '0.1'
assert ns['release'] == '0.1'
- assert ns['html_static_path'] == ['.static']
+ assert ns['html_static_path'] == ['_static']
assert ns['latex_documents'] == [
('index', 'SphinxTest.tex', 'Sphinx Test Documentation',
'Georg Brandl', 'manual')]
- assert (tempdir / '.static').isdir()
- assert (tempdir / '.templates').isdir()
+ assert (tempdir / '_static').isdir()
+ assert (tempdir / '_templates').isdir()
assert (tempdir / 'index.rst').isfile()
assert (tempdir / 'Makefile').isfile()
+ assert (tempdir / 'make.bat').isfile()
+
@with_tempdir
def test_quickstart_all_answers(tempdir):
answers = {
'Root path': tempdir,
'Separate source and build': 'y',
- 'Name prefix for templates': '_',
+ 'Name prefix for templates': '.',
'Project name': 'STASI\xe2\x84\xa2',
'Author name': 'Wolfgang Sch\xc3\xa4uble & G. Beckstein',
'Project version': '2.0',
@@ -118,7 +121,13 @@ def test_quickstart_all_answers(tempdir):
'autodoc': 'y',
'doctest': 'yes',
'intersphinx': 'no',
+ 'todo': 'n',
+ 'coverage': 'no',
+ 'pngmath': 'N',
+ 'jsmath': 'no',
+ 'ifconfig': 'no',
'Create Makefile': 'no',
+ 'Create Windows command file': 'no',
}
qs.raw_input = mock_raw_input(answers, needanswer=True)
qs.TERM_ENCODING = 'utf-8'
@@ -129,7 +138,7 @@ def test_quickstart_all_answers(tempdir):
ns = {}
execfile(conffile, ns)
assert ns['extensions'] == ['sphinx.ext.autodoc', 'sphinx.ext.doctest']
- assert ns['templates_path'] == ['_templates']
+ assert ns['templates_path'] == ['.templates']
assert ns['source_suffix'] == '.txt'
assert ns['master_doc'] == 'contents'
assert ns['project'] == u'STASI™'
@@ -137,12 +146,12 @@ def test_quickstart_all_answers(tempdir):
time.strftime('%Y')
assert ns['version'] == '2.0'
assert ns['release'] == '2.0.1'
- assert ns['html_static_path'] == ['_static']
+ assert ns['html_static_path'] == ['.static']
assert ns['latex_documents'] == [
('contents', 'STASI.tex', u'STASI™ Documentation',
ur'Wolfgang Schäuble \& G. Beckstein', 'manual')]
assert (tempdir / 'build').isdir()
- assert (tempdir / 'source' / '_static').isdir()
- assert (tempdir / 'source' / '_templates').isdir()
+ assert (tempdir / 'source' / '.static').isdir()
+ assert (tempdir / 'source' / '.templates').isdir()
assert (tempdir / 'source' / 'contents.txt').isfile()
diff --git a/tests/test_theming.py b/tests/test_theming.py
new file mode 100644
index 00000000..349a9ce4
--- /dev/null
+++ b/tests/test_theming.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+"""
+ test_theming
+ ~~~~~~~~~~~~
+
+ Test the Theme class.
+
+ :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import os
+import zipfile
+
+from util import *
+
+from sphinx.theming import Theme, ThemeError
+
+
+@with_app(confoverrides={'html_theme': 'ziptheme',
+ 'html_theme_options.testopt': 'foo'})
+def test_theme_api(app):
+ cfg = app.config
+
+ # test Theme class API
+ assert set(Theme.themes.keys()) == \
+ set(['basic', 'default', 'sphinxdoc', 'traditional',
+ 'testtheme', 'ziptheme'])
+ assert Theme.themes['testtheme'][1] is None
+ assert isinstance(Theme.themes['ziptheme'][1], zipfile.ZipFile)
+
+ # test Theme instance API
+ theme = app.builder.theme
+ assert theme.name == 'ziptheme'
+ assert theme.themedir_created
+ themedir = theme.themedir
+ assert theme.base.name == 'basic'
+ assert len(theme.get_dirchain()) == 2
+
+ # direct setting
+ assert theme.get_confstr('theme', 'stylesheet') == 'custom.css'
+ # inherited setting
+ assert theme.get_confstr('options', 'nosidebar') == 'false'
+ # nonexisting setting
+ assert theme.get_confstr('theme', 'foobar', 'def') == 'def'
+ raises(ThemeError, theme.get_confstr, 'theme', 'foobar')
+
+ # options API
+ raises(ThemeError, theme.get_options, {'nonexisting': 'foo'})
+ options = theme.get_options(cfg.html_theme_options)
+ assert options['testopt'] == 'foo'
+ assert options['nosidebar'] == 'false'
+
+ # cleanup temp directories
+ theme.cleanup()
+ assert not os.path.exists(themedir)
diff --git a/tests/util.py b/tests/util.py
index 2cd2c031..4bb6a653 100644
--- a/tests/util.py
+++ b/tests/util.py
@@ -19,7 +19,8 @@ except ImportError:
# functools is new in 2.4
wraps = lambda f: (lambda w: w)
-from sphinx import application, builder
+from sphinx import application
+from sphinx.ext.autodoc import AutoDirective
from path import path
@@ -29,7 +30,7 @@ from nose import tools
__all__ = [
'test_root',
'raises', 'raises_msg', 'Struct',
- 'ListOutput', 'TestApp', 'with_app',
+ 'ListOutput', 'TestApp', 'with_app', 'gen_with_app',
'path', 'with_tempdir', 'write_file',
'sprint',
]
@@ -97,12 +98,14 @@ class TestApp(application.Sphinx):
"""
def __init__(self, srcdir=None, confdir=None, outdir=None, doctreedir=None,
- buildername='html', confoverrides=None, status=None, warning=None,
- freshenv=None, confname='conf.py', cleanenv=False):
+ buildername='html', confoverrides=None,
+ status=None, warning=None, freshenv=None,
+ warningiserror=None, tags=None,
+ confname='conf.py', cleanenv=False):
application.CONFIG_FILENAME = confname
- self.cleanup_trees = []
+ self.cleanup_trees = [test_root / 'generated']
if srcdir is None:
srcdir = test_root
@@ -134,12 +137,15 @@ class TestApp(application.Sphinx):
warning = ListOutput('stderr')
if freshenv is None:
freshenv = False
+ if warningiserror is None:
+ warningiserror = False
application.Sphinx.__init__(self, srcdir, confdir, outdir, doctreedir,
buildername, confoverrides, status, warning,
- freshenv)
+ freshenv, warningiserror, tags)
def cleanup(self, doctrees=False):
+ AutoDirective._registry.clear()
for tree in self.cleanup_trees:
shutil.rmtree(tree, True)
@@ -153,10 +159,26 @@ def with_app(*args, **kwargs):
@wraps(func)
def deco(*args2, **kwargs2):
app = TestApp(*args, **kwargs)
- try:
- func(app, *args2, **kwargs2)
- finally:
- app.cleanup()
+ func(app, *args2, **kwargs2)
+ # don't execute cleanup if test failed
+ app.cleanup()
+ return deco
+ return generator
+
+
+def gen_with_app(*args, **kwargs):
+ """
+ Make a TestApp with args and kwargs, pass it to the test and clean up
+ properly.
+ """
+ def generator(func):
+ @wraps(func)
+ def deco(*args2, **kwargs2):
+ app = TestApp(*args, **kwargs)
+ for item in func(app, *args2, **kwargs2):
+ yield item
+ # don't execute cleanup if test failed
+ app.cleanup()
return deco
return generator