summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorDerek Anderson <public@kered.org>2006-10-26 19:09:51 +0000
committerDerek Anderson <public@kered.org>2006-10-26 19:09:51 +0000
commit42851d90dadbf62f5d342ce5c4f496ba1eeba987 (patch)
treea5d0e5c178afb2d7dbb7bf5ab37db9ced42f4b52 /tests
parent450889c9a6f7da3c2fce77a0ccf4c4cea9e29710 (diff)
committing to schema-evolution
merge from HEAD git-svn-id: http://code.djangoproject.com/svn/django/branches/schema-evolution@3937 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'tests')
-rw-r--r--tests/doctest.py2665
-rw-r--r--tests/modeltests/basic/models.py15
-rw-r--r--tests/modeltests/choices/models.py4
-rw-r--r--tests/modeltests/custom_columns/models.py4
-rw-r--r--tests/modeltests/custom_managers/models.py4
-rw-r--r--tests/modeltests/custom_methods/models.py4
-rw-r--r--tests/modeltests/custom_pk/models.py8
-rw-r--r--tests/modeltests/empty/models.py6
-rw-r--r--tests/modeltests/field_defaults/models.py4
-rw-r--r--tests/modeltests/generic_relations/models.py4
-rw-r--r--tests/modeltests/get_latest/models.py12
-rw-r--r--tests/modeltests/get_or_create/models.py4
-rw-r--r--tests/modeltests/invalid_models/models.py39
-rw-r--r--tests/modeltests/lookup/models.py15
-rw-r--r--tests/modeltests/m2m_and_m2o/models.py4
-rw-r--r--tests/modeltests/m2m_intermediary/models.py4
-rw-r--r--tests/modeltests/m2m_multiple/models.py4
-rw-r--r--tests/modeltests/m2m_recursive/models.py4
-rw-r--r--tests/modeltests/m2o_recursive/models.py4
-rw-r--r--tests/modeltests/m2o_recursive2/models.py4
-rw-r--r--tests/modeltests/manipulators/models.py4
-rw-r--r--tests/modeltests/many_to_many/models.py4
-rw-r--r--tests/modeltests/many_to_one/models.py4
-rw-r--r--tests/modeltests/many_to_one_null/models.py4
-rw-r--r--tests/modeltests/model_inheritance/models.py4
-rw-r--r--tests/modeltests/mutually_referential/models.py4
-rw-r--r--tests/modeltests/one_to_one/models.py18
-rw-r--r--tests/modeltests/or_lookups/models.py4
-rw-r--r--tests/modeltests/ordering/models.py4
-rw-r--r--tests/modeltests/pagination/models.py4
-rw-r--r--tests/modeltests/properties/models.py4
-rw-r--r--tests/modeltests/reserved_names/models.py4
-rw-r--r--tests/modeltests/reverse_lookup/models.py4
-rw-r--r--tests/modeltests/save_delete_hooks/models.py4
-rw-r--r--tests/modeltests/serializers/models.py4
-rw-r--r--tests/modeltests/str/models.py4
-rw-r--r--tests/modeltests/test_client/__init__.py (renamed from tests/othertests/__init__.py)0
-rw-r--r--tests/modeltests/test_client/management.py10
-rw-r--r--tests/modeltests/test_client/models.py101
-rw-r--r--tests/modeltests/test_client/urls.py9
-rw-r--r--tests/modeltests/test_client/views.py35
-rw-r--r--tests/modeltests/transactions/models.py6
-rw-r--r--tests/modeltests/validation/models.py4
-rw-r--r--tests/othertests/cache.py60
-rw-r--r--tests/othertests/markup.py68
-rw-r--r--tests/othertests/templates.py625
-rw-r--r--tests/regressiontests/cache/__init__.py0
-rw-r--r--tests/regressiontests/cache/models.py0
-rw-r--r--tests/regressiontests/cache/tests.py71
-rw-r--r--tests/regressiontests/dateformat/__init__.py0
-rw-r--r--tests/regressiontests/dateformat/models.py0
-rw-r--r--tests/regressiontests/dateformat/tests.py (renamed from tests/othertests/dateformat.py)42
-rw-r--r--tests/regressiontests/db_typecasts/__init__.py0
-rw-r--r--tests/regressiontests/db_typecasts/models.py0
-rw-r--r--tests/regressiontests/db_typecasts/tests.py (renamed from tests/othertests/db_typecasts.py)15
-rw-r--r--tests/regressiontests/defaultfilters/__init__.py0
-rw-r--r--tests/regressiontests/defaultfilters/models.py0
-rw-r--r--tests/regressiontests/defaultfilters/tests.py (renamed from tests/othertests/defaultfilters.py)6
-rw-r--r--tests/regressiontests/httpwrappers/__init__.py0
-rw-r--r--tests/regressiontests/httpwrappers/models.py0
-rw-r--r--tests/regressiontests/httpwrappers/tests.py (renamed from tests/othertests/httpwrappers.py)0
-rw-r--r--tests/regressiontests/initial_sql_regress/models.py2
-rw-r--r--tests/regressiontests/initial_sql_regress/sql/simple.sql1
-rw-r--r--tests/regressiontests/many_to_one_regress/models.py2
-rw-r--r--tests/regressiontests/markup/__init__.py0
-rw-r--r--tests/regressiontests/markup/models.py0
-rw-r--r--tests/regressiontests/markup/tests.py69
-rw-r--r--tests/regressiontests/null_queries/__init__.py0
-rw-r--r--tests/regressiontests/null_queries/models.py54
-rw-r--r--tests/regressiontests/one_to_one_regress/models.py4
-rw-r--r--tests/regressiontests/string_lookup/models.py4
-rw-r--r--tests/regressiontests/templates/__init__.py0
-rw-r--r--tests/regressiontests/templates/models.py0
-rw-r--r--tests/regressiontests/templates/tests.py667
-rw-r--r--tests/regressiontests/urlpatterns_reverse/__init__.py0
-rw-r--r--tests/regressiontests/urlpatterns_reverse/models.py0
-rw-r--r--tests/regressiontests/urlpatterns_reverse/tests.py39
-rwxr-xr-xtests/runtests.py340
-rw-r--r--tests/templates/404.html1
-rw-r--r--tests/templates/500.html1
-rw-r--r--tests/templates/login.html19
-rw-r--r--tests/urls.py10
82 files changed, 1371 insertions, 3784 deletions
diff --git a/tests/doctest.py b/tests/doctest.py
deleted file mode 100644
index df7aa978d3..0000000000
--- a/tests/doctest.py
+++ /dev/null
@@ -1,2665 +0,0 @@
-# Module doctest.
-# Released to the public domain 16-Jan-2001, by Tim Peters (tim@python.org).
-# Major enhancements and refactoring by:
-# Jim Fulton
-# Edward Loper
-
-# Provided as-is; use at your own risk; no warranty; no promises; enjoy!
-
-r"""Module doctest -- a framework for running examples in docstrings.
-
-In simplest use, end each module M to be tested with:
-
-def _test():
- import doctest
- doctest.testmod()
-
-if __name__ == "__main__":
- _test()
-
-Then running the module as a script will cause the examples in the
-docstrings to get executed and verified:
-
-python M.py
-
-This won't display anything unless an example fails, in which case the
-failing example(s) and the cause(s) of the failure(s) are printed to stdout
-(why not stderr? because stderr is a lame hack <0.2 wink>), and the final
-line of output is "Test failed.".
-
-Run it with the -v switch instead:
-
-python M.py -v
-
-and a detailed report of all examples tried is printed to stdout, along
-with assorted summaries at the end.
-
-You can force verbose mode by passing "verbose=True" to testmod, or prohibit
-it by passing "verbose=False". In either of those cases, sys.argv is not
-examined by testmod.
-
-There are a variety of other ways to run doctests, including integration
-with the unittest framework, and support for running non-Python text
-files containing doctests. There are also many ways to override parts
-of doctest's default behaviors. See the Library Reference Manual for
-details.
-"""
-
-__docformat__ = 'reStructuredText en'
-
-__all__ = [
- # 0, Option Flags
- 'register_optionflag',
- 'DONT_ACCEPT_TRUE_FOR_1',
- 'DONT_ACCEPT_BLANKLINE',
- 'NORMALIZE_WHITESPACE',
- 'ELLIPSIS',
- 'IGNORE_EXCEPTION_DETAIL',
- 'COMPARISON_FLAGS',
- 'REPORT_UDIFF',
- 'REPORT_CDIFF',
- 'REPORT_NDIFF',
- 'REPORT_ONLY_FIRST_FAILURE',
- 'REPORTING_FLAGS',
- # 1. Utility Functions
- 'is_private',
- # 2. Example & DocTest
- 'Example',
- 'DocTest',
- # 3. Doctest Parser
- 'DocTestParser',
- # 4. Doctest Finder
- 'DocTestFinder',
- # 5. Doctest Runner
- 'DocTestRunner',
- 'OutputChecker',
- 'DocTestFailure',
- 'UnexpectedException',
- 'DebugRunner',
- # 6. Test Functions
- 'testmod',
- 'testfile',
- 'run_docstring_examples',
- # 7. Tester
- 'Tester',
- # 8. Unittest Support
- 'DocTestSuite',
- 'DocFileSuite',
- 'set_unittest_reportflags',
- # 9. Debugging Support
- 'script_from_examples',
- 'testsource',
- 'debug_src',
- 'debug',
-]
-
-import __future__
-
-import sys, traceback, inspect, linecache, os, re, types
-import unittest, difflib, pdb, tempfile
-import warnings
-from StringIO import StringIO
-
-# Don't whine about the deprecated is_private function in this
-# module's tests.
-warnings.filterwarnings("ignore", "is_private", DeprecationWarning,
- __name__, 0)
-
-# There are 4 basic classes:
-# - Example: a <source, want> pair, plus an intra-docstring line number.
-# - DocTest: a collection of examples, parsed from a docstring, plus
-# info about where the docstring came from (name, filename, lineno).
-# - DocTestFinder: extracts DocTests from a given object's docstring and
-# its contained objects' docstrings.
-# - DocTestRunner: runs DocTest cases, and accumulates statistics.
-#
-# So the basic picture is:
-#
-# list of:
-# +------+ +---------+ +-------+
-# |object| --DocTestFinder-> | DocTest | --DocTestRunner-> |results|
-# +------+ +---------+ +-------+
-# | Example |
-# | ... |
-# | Example |
-# +---------+
-
-# Option constants.
-
-OPTIONFLAGS_BY_NAME = {}
-def register_optionflag(name):
- flag = 1 << len(OPTIONFLAGS_BY_NAME)
- OPTIONFLAGS_BY_NAME[name] = flag
- return flag
-
-DONT_ACCEPT_TRUE_FOR_1 = register_optionflag('DONT_ACCEPT_TRUE_FOR_1')
-DONT_ACCEPT_BLANKLINE = register_optionflag('DONT_ACCEPT_BLANKLINE')
-NORMALIZE_WHITESPACE = register_optionflag('NORMALIZE_WHITESPACE')
-ELLIPSIS = register_optionflag('ELLIPSIS')
-IGNORE_EXCEPTION_DETAIL = register_optionflag('IGNORE_EXCEPTION_DETAIL')
-
-COMPARISON_FLAGS = (DONT_ACCEPT_TRUE_FOR_1 |
- DONT_ACCEPT_BLANKLINE |
- NORMALIZE_WHITESPACE |
- ELLIPSIS |
- IGNORE_EXCEPTION_DETAIL)
-
-REPORT_UDIFF = register_optionflag('REPORT_UDIFF')
-REPORT_CDIFF = register_optionflag('REPORT_CDIFF')
-REPORT_NDIFF = register_optionflag('REPORT_NDIFF')
-REPORT_ONLY_FIRST_FAILURE = register_optionflag('REPORT_ONLY_FIRST_FAILURE')
-
-REPORTING_FLAGS = (REPORT_UDIFF |
- REPORT_CDIFF |
- REPORT_NDIFF |
- REPORT_ONLY_FIRST_FAILURE)
-
-# Special string markers for use in `want` strings:
-BLANKLINE_MARKER = '<BLANKLINE>'
-ELLIPSIS_MARKER = '...'
-
-######################################################################
-## Table of Contents
-######################################################################
-# 1. Utility Functions
-# 2. Example & DocTest -- store test cases
-# 3. DocTest Parser -- extracts examples from strings
-# 4. DocTest Finder -- extracts test cases from objects
-# 5. DocTest Runner -- runs test cases
-# 6. Test Functions -- convenient wrappers for testing
-# 7. Tester Class -- for backwards compatibility
-# 8. Unittest Support
-# 9. Debugging Support
-# 10. Example Usage
-
-######################################################################
-## 1. Utility Functions
-######################################################################
-
-def is_private(prefix, base):
- """prefix, base -> true iff name prefix + "." + base is "private".
-
- Prefix may be an empty string, and base does not contain a period.
- Prefix is ignored (although functions you write conforming to this
- protocol may make use of it).
- Return true iff base begins with an (at least one) underscore, but
- does not both begin and end with (at least) two underscores.
-
- >>> is_private("a.b", "my_func")
- False
- >>> is_private("____", "_my_func")
- True
- >>> is_private("someclass", "__init__")
- False
- >>> is_private("sometypo", "__init_")
- True
- >>> is_private("x.y.z", "_")
- True
- >>> is_private("_x.y.z", "__")
- False
- >>> is_private("", "") # senseless but consistent
- False
- """
- warnings.warn("is_private is deprecated; it wasn't useful; "
- "examine DocTestFinder.find() lists instead",
- DeprecationWarning, stacklevel=2)
- return base[:1] == "_" and not base[:2] == "__" == base[-2:]
-
-def _extract_future_flags(globs):
- """
- Return the compiler-flags associated with the future features that
- have been imported into the given namespace (globs).
- """
- flags = 0
- for fname in __future__.all_feature_names:
- feature = globs.get(fname, None)
- if feature is getattr(__future__, fname):
- flags |= feature.compiler_flag
- return flags
-
-def _normalize_module(module, depth=2):
- """
- Return the module specified by `module`. In particular:
- - If `module` is a module, then return module.
- - If `module` is a string, then import and return the
- module with that name.
- - If `module` is None, then return the calling module.
- The calling module is assumed to be the module of
- the stack frame at the given depth in the call stack.
- """
- if inspect.ismodule(module):
- return module
- elif isinstance(module, (str, unicode)):
- return __import__(module, globals(), locals(), ["*"])
- elif module is None:
- return sys.modules[sys._getframe(depth).f_globals['__name__']]
- else:
- raise TypeError("Expected a module, string, or None")
-
-def _indent(s, indent=4):
- """
- Add the given number of space characters to the beginning every
- non-blank line in `s`, and return the result.
- """
- # This regexp matches the start of non-blank lines:
- return re.sub('(?m)^(?!$)', indent*' ', s)
-
-def _exception_traceback(exc_info):
- """
- Return a string containing a traceback message for the given
- exc_info tuple (as returned by sys.exc_info()).
- """
- # Get a traceback message.
- excout = StringIO()
- exc_type, exc_val, exc_tb = exc_info
- traceback.print_exception(exc_type, exc_val, exc_tb, file=excout)
- return excout.getvalue()
-
-# Override some StringIO methods.
-class _SpoofOut(StringIO):
- def getvalue(self):
- result = StringIO.getvalue(self)
- # If anything at all was written, make sure there's a trailing
- # newline. There's no way for the expected output to indicate
- # that a trailing newline is missing.
- if result and not result.endswith("\n"):
- result += "\n"
- # Prevent softspace from screwing up the next test case, in
- # case they used print with a trailing comma in an example.
- if hasattr(self, "softspace"):
- del self.softspace
- return result
-
- def truncate(self, size=None):
- StringIO.truncate(self, size)
- if hasattr(self, "softspace"):
- del self.softspace
-
-# Worst-case linear-time ellipsis matching.
-def _ellipsis_match(want, got):
- """
- Essentially the only subtle case:
- >>> _ellipsis_match('aa...aa', 'aaa')
- False
- """
- if ELLIPSIS_MARKER not in want:
- return want == got
-
- # Find "the real" strings.
- ws = want.split(ELLIPSIS_MARKER)
- assert len(ws) >= 2
-
- # Deal with exact matches possibly needed at one or both ends.
- startpos, endpos = 0, len(got)
- w = ws[0]
- if w: # starts with exact match
- if got.startswith(w):
- startpos = len(w)
- del ws[0]
- else:
- return False
- w = ws[-1]
- if w: # ends with exact match
- if got.endswith(w):
- endpos -= len(w)
- del ws[-1]
- else:
- return False
-
- if startpos > endpos:
- # Exact end matches required more characters than we have, as in
- # _ellipsis_match('aa...aa', 'aaa')
- return False
-
- # For the rest, we only need to find the leftmost non-overlapping
- # match for each piece. If there's no overall match that way alone,
- # there's no overall match period.
- for w in ws:
- # w may be '' at times, if there are consecutive ellipses, or
- # due to an ellipsis at the start or end of `want`. That's OK.
- # Search for an empty string succeeds, and doesn't change startpos.
- startpos = got.find(w, startpos, endpos)
- if startpos < 0:
- return False
- startpos += len(w)
-
- return True
-
-def _comment_line(line):
- "Return a commented form of the given line"
- line = line.rstrip()
- if line:
- return '# '+line
- else:
- return '#'
-
-class _OutputRedirectingPdb(pdb.Pdb):
- """
- A specialized version of the python debugger that redirects stdout
- to a given stream when interacting with the user. Stdout is *not*
- redirected when traced code is executed.
- """
- def __init__(self, out):
- self.__out = out
- pdb.Pdb.__init__(self)
-
- def trace_dispatch(self, *args):
- # Redirect stdout to the given stream.
- save_stdout = sys.stdout
- sys.stdout = self.__out
- # Call Pdb's trace dispatch method.
- try:
- return pdb.Pdb.trace_dispatch(self, *args)
- finally:
- sys.stdout = save_stdout
-
-# [XX] Normalize with respect to os.path.pardir?
-def _module_relative_path(module, path):
- if not inspect.ismodule(module):
- raise TypeError, 'Expected a module: %r' % module
- if path.startswith('/'):
- raise ValueError, 'Module-relative files may not have absolute paths'
-
- # Find the base directory for the path.
- if hasattr(module, '__file__'):
- # A normal module/package
- basedir = os.path.split(module.__file__)[0]
- elif module.__name__ == '__main__':
- # An interactive session.
- if len(sys.argv)>0 and sys.argv[0] != '':
- basedir = os.path.split(sys.argv[0])[0]
- else:
- basedir = os.curdir
- else:
- # A module w/o __file__ (this includes builtins)
- raise ValueError("Can't resolve paths relative to the module " +
- module + " (it has no __file__)")
-
- # Combine the base directory and the path.
- return os.path.join(basedir, *(path.split('/')))
-
-######################################################################
-## 2. Example & DocTest
-######################################################################
-## - An "example" is a <source, want> pair, where "source" is a
-## fragment of source code, and "want" is the expected output for
-## "source." The Example class also includes information about
-## where the example was extracted from.
-##
-## - A "doctest" is a collection of examples, typically extracted from
-## a string (such as an object's docstring). The DocTest class also
-## includes information about where the string was extracted from.
-
-class Example:
- """
- A single doctest example, consisting of source code and expected
- output. `Example` defines the following attributes:
-
- - source: A single Python statement, always ending with a newline.
- The constructor adds a newline if needed.
-
- - want: The expected output from running the source code (either
- from stdout, or a traceback in case of exception). `want` ends
- with a newline unless it's empty, in which case it's an empty
- string. The constructor adds a newline if needed.
-
- - exc_msg: The exception message generated by the example, if
- the example is expected to generate an exception; or `None` if
- it is not expected to generate an exception. This exception
- message is compared against the return value of
- `traceback.format_exception_only()`. `exc_msg` ends with a
- newline unless it's `None`. The constructor adds a newline
- if needed.
-
- - lineno: The line number within the DocTest string containing
- this Example where the Example begins. This line number is
- zero-based, with respect to the beginning of the DocTest.
-
- - indent: The example's indentation in the DocTest string.
- I.e., the number of space characters that preceed the
- example's first prompt.
-
- - options: A dictionary mapping from option flags to True or
- False, which is used to override default options for this
- example. Any option flags not contained in this dictionary
- are left at their default value (as specified by the
- DocTestRunner's optionflags). By default, no options are set.
- """
- def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
- options=None):
- # Normalize inputs.
- if not source.endswith('\n'):
- source += '\n'
- if want and not want.endswith('\n'):
- want += '\n'
- if exc_msg is not None and not exc_msg.endswith('\n'):
- exc_msg += '\n'
- # Store properties.
- self.source = source
- self.want = want
- self.lineno = lineno
- self.indent = indent
- if options is None: options = {}
- self.options = options
- self.exc_msg = exc_msg
-
-class DocTest:
- """
- A collection of doctest examples that should be run in a single
- namespace. Each `DocTest` defines the following attributes:
-
- - examples: the list of examples.
-
- - globs: The namespace (aka globals) that the examples should
- be run in.
-
- - name: A name identifying the DocTest (typically, the name of
- the object whose docstring this DocTest was extracted from).
-
- - filename: The name of the file that this DocTest was extracted
- from, or `None` if the filename is unknown.
-
- - lineno: The line number within filename where this DocTest
- begins, or `None` if the line number is unavailable. This
- line number is zero-based, with respect to the beginning of
- the file.
-
- - docstring: The string that the examples were extracted from,
- or `None` if the string is unavailable.
- """
- def __init__(self, examples, globs, name, filename, lineno, docstring):
- """
- Create a new DocTest containing the given examples. The
- DocTest's globals are initialized with a copy of `globs`.
- """
- assert not isinstance(examples, basestring), \
- "DocTest no longer accepts str; use DocTestParser instead"
- self.examples = examples
- self.docstring = docstring
- self.globs = globs.copy()
- self.name = name
- self.filename = filename
- self.lineno = lineno
-
- def __repr__(self):
- if len(self.examples) == 0:
- examples = 'no examples'
- elif len(self.examples) == 1:
- examples = '1 example'
- else:
- examples = '%d examples' % len(self.examples)
- return ('<DocTest %s from %s:%s (%s)>' %
- (self.name, self.filename, self.lineno, examples))
-
-
- # This lets us sort tests by name:
- def __cmp__(self, other):
- if not isinstance(other, DocTest):
- return -1
- return cmp((self.name, self.filename, self.lineno, id(self)),
- (other.name, other.filename, other.lineno, id(other)))
-
-######################################################################
-## 3. DocTestParser
-######################################################################
-
-class DocTestParser:
- """
- A class used to parse strings containing doctest examples.
- """
- # This regular expression is used to find doctest examples in a
- # string. It defines three groups: `source` is the source code
- # (including leading indentation and prompts); `indent` is the
- # indentation of the first (PS1) line of the source code; and
- # `want` is the expected output (including leading indentation).
- _EXAMPLE_RE = re.compile(r'''
- # Source consists of a PS1 line followed by zero or more PS2 lines.
- (?P<source>
- (?:^(?P<indent> [ ]*) >>> .*) # PS1 line
- (?:\n [ ]* \.\.\. .*)*) # PS2 lines
- \n?
- # Want consists of any non-blank lines that do not start with PS1.
- (?P<want> (?:(?![ ]*$) # Not a blank line
- (?![ ]*>>>) # Not a line starting with PS1
- .*$\n? # But any other line
- )*)
- ''', re.MULTILINE | re.VERBOSE)
-
- # A regular expression for handling `want` strings that contain
- # expected exceptions. It divides `want` into three pieces:
- # - the traceback header line (`hdr`)
- # - the traceback stack (`stack`)
- # - the exception message (`msg`), as generated by
- # traceback.format_exception_only()
- # `msg` may have multiple lines. We assume/require that the
- # exception message is the first non-indented line starting with a word
- # character following the traceback header line.
- _EXCEPTION_RE = re.compile(r"""
- # Grab the traceback header. Different versions of Python have
- # said different things on the first traceback line.
- ^(?P<hdr> Traceback\ \(
- (?: most\ recent\ call\ last
- | innermost\ last
- ) \) :
- )
- \s* $ # toss trailing whitespace on the header.
- (?P<stack> .*?) # don't blink: absorb stuff until...
- ^ (?P<msg> \w+ .*) # a line *starts* with alphanum.
- """, re.VERBOSE | re.MULTILINE | re.DOTALL)
-
- # A callable returning a true value iff its argument is a blank line
- # or contains a single comment.
- _IS_BLANK_OR_COMMENT = re.compile(r'^[ ]*(#.*)?$').match
-
- def parse(self, string, name='<string>'):
- """
- Divide the given string into examples and intervening text,
- and return them as a list of alternating Examples and strings.
- Line numbers for the Examples are 0-based. The optional
- argument `name` is a name identifying this string, and is only
- used for error messages.
- """
- string = string.expandtabs()
- # If all lines begin with the same indentation, then strip it.
- min_indent = self._min_indent(string)
- if min_indent > 0:
- string = '\n'.join([l[min_indent:] for l in string.split('\n')])
-
- output = []
- charno, lineno = 0, 0
- # Find all doctest examples in the string:
- for m in self._EXAMPLE_RE.finditer(string):
- # Add the pre-example text to `output`.
- output.append(string[charno:m.start()])
- # Update lineno (lines before this example)
- lineno += string.count('\n', charno, m.start())
- # Extract info from the regexp match.
- (source, options, want, exc_msg) = \
- self._parse_example(m, name, lineno)
- # Create an Example, and add it to the list.
- if not self._IS_BLANK_OR_COMMENT(source):
- output.append( Example(source, want, exc_msg,
- lineno=lineno,
- indent=min_indent+len(m.group('indent')),
- options=options) )
- # Update lineno (lines inside this example)
- lineno += string.count('\n', m.start(), m.end())
- # Update charno.
- charno = m.end()
- # Add any remaining post-example text to `output`.
- output.append(string[charno:])
- return output
-
- def get_doctest(self, string, globs, name, filename, lineno):
- """
- Extract all doctest examples from the given string, and
- collect them into a `DocTest` object.
-
- `globs`, `name`, `filename`, and `lineno` are attributes for
- the new `DocTest` object. See the documentation for `DocTest`
- for more information.
- """
- return DocTest(self.get_examples(string, name), globs,
- name, filename, lineno, string)
-
- def get_examples(self, string, name='<string>'):
- """
- Extract all doctest examples from the given string, and return
- them as a list of `Example` objects. Line numbers are
- 0-based, because it's most common in doctests that nothing
- interesting appears on the same line as opening triple-quote,
- and so the first interesting line is called \"line 1\" then.
-
- The optional argument `name` is a name identifying this
- string, and is only used for error messages.
- """
- return [x for x in self.parse(string, name)
- if isinstance(x, Example)]
-
- def _parse_example(self, m, name, lineno):
- """
- Given a regular expression match from `_EXAMPLE_RE` (`m`),
- return a pair `(source, want)`, where `source` is the matched
- example's source code (with prompts and indentation stripped);
- and `want` is the example's expected output (with indentation
- stripped).
-
- `name` is the string's name, and `lineno` is the line number
- where the example starts; both are used for error messages.
- """
- # Get the example's indentation level.
- indent = len(m.group('indent'))
-
- # Divide source into lines; check that they're properly
- # indented; and then strip their indentation & prompts.
- source_lines = m.group('source').split('\n')
- self._check_prompt_blank(source_lines, indent, name, lineno)
- self._check_prefix(source_lines[1:], ' '*indent + '.', name, lineno)
- source = '\n'.join([sl[indent+4:] for sl in source_lines])
-
- # Divide want into lines; check that it's properly indented; and
- # then strip the indentation. Spaces before the last newline should
- # be preserved, so plain rstrip() isn't good enough.
- want = m.group('want')
- want_lines = want.split('\n')
- if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
- del want_lines[-1] # forget final newline & spaces after it
- self._check_prefix(want_lines, ' '*indent, name,
- lineno + len(source_lines))
- want = '\n'.join([wl[indent:] for wl in want_lines])
-
- # If `want` contains a traceback message, then extract it.
- m = self._EXCEPTION_RE.match(want)
- if m:
- exc_msg = m.group('msg')
- else:
- exc_msg = None
-
- # Extract options from the source.
- options = self._find_options(source, name, lineno)
-
- return source, options, want, exc_msg
-
- # This regular expression looks for option directives in the
- # source code of an example. Option directives are comments
- # starting with "doctest:". Warning: this may give false
- # positives for string-literals that contain the string
- # "#doctest:". Eliminating these false positives would require
- # actually parsing the string; but we limit them by ignoring any
- # line containing "#doctest:" that is *followed* by a quote mark.
- _OPTION_DIRECTIVE_RE = re.compile(r'#\s*doctest:\s*([^\n\'"]*)$',
- re.MULTILINE)
-
- def _find_options(self, source, name, lineno):
- """
- Return a dictionary containing option overrides extracted from
- option directives in the given source string.
-
- `name` is the string's name, and `lineno` is the line number
- where the example starts; both are used for error messages.
- """
- options = {}
- # (note: with the current regexp, this will match at most once:)
- for m in self._OPTION_DIRECTIVE_RE.finditer(source):
- option_strings = m.group(1).replace(',', ' ').split()
- for option in option_strings:
- if (option[0] not in '+-' or
- option[1:] not in OPTIONFLAGS_BY_NAME):
- raise ValueError('line %r of the doctest for %s '
- 'has an invalid option: %r' %
- (lineno+1, name, option))
- flag = OPTIONFLAGS_BY_NAME[option[1:]]
- options[flag] = (option[0] == '+')
- if options and self._IS_BLANK_OR_COMMENT(source):
- raise ValueError('line %r of the doctest for %s has an option '
- 'directive on a line with no example: %r' %
- (lineno, name, source))
- return options
-
- # This regular expression finds the indentation of every non-blank
- # line in a string.
- _INDENT_RE = re.compile('^([ ]*)(?=\S)', re.MULTILINE)
-
- def _min_indent(self, s):
- "Return the minimum indentation of any non-blank line in `s`"
- indents = [len(indent) for indent in self._INDENT_RE.findall(s)]
- if len(indents) > 0:
- return min(indents)
- else:
- return 0
-
- def _check_prompt_blank(self, lines, indent, name, lineno):
- """
- Given the lines of a source string (including prompts and
- leading indentation), check to make sure that every prompt is
- followed by a space character. If any line is not followed by
- a space character, then raise ValueError.
- """
- for i, line in enumerate(lines):
- if len(line) >= indent+4 and line[indent+3] != ' ':
- raise ValueError('line %r of the docstring for %s '
- 'lacks blank after %s: %r' %
- (lineno+i+1, name,
- line[indent:indent+3], line))
-
- def _check_prefix(self, lines, prefix, name, lineno):
- """
- Check that every line in the given list starts with the given
- prefix; if any line does not, then raise a ValueError.
- """
- for i, line in enumerate(lines):
- if line and not line.startswith(prefix):
- raise ValueError('line %r of the docstring for %s has '
- 'inconsistent leading whitespace: %r' %
- (lineno+i+1, name, line))
-
-
-######################################################################
-## 4. DocTest Finder
-######################################################################
-
-class DocTestFinder:
- """
- A class used to extract the DocTests that are relevant to a given
- object, from its docstring and the docstrings of its contained
- objects. Doctests can currently be extracted from the following
- object types: modules, functions, classes, methods, staticmethods,
- classmethods, and properties.
- """
-
- def __init__(self, verbose=False, parser=DocTestParser(),
- recurse=True, _namefilter=None, exclude_empty=True):
- """
- Create a new doctest finder.
-
- The optional argument `parser` specifies a class or
- function that should be used to create new DocTest objects (or
- objects that implement the same interface as DocTest). The
- signature for this factory function should match the signature
- of the DocTest constructor.
-
- If the optional argument `recurse` is false, then `find` will
- only examine the given object, and not any contained objects.
-
- If the optional argument `exclude_empty` is false, then `find`
- will include tests for objects with empty docstrings.
- """
- self._parser = parser
- self._verbose = verbose
- self._recurse = recurse
- self._exclude_empty = exclude_empty
- # _namefilter is undocumented, and exists only for temporary backward-
- # compatibility support of testmod's deprecated isprivate mess.
- self._namefilter = _namefilter
-
- def find(self, obj, name=None, module=None, globs=None,
- extraglobs=None):
- """
- Return a list of the DocTests that are defined by the given
- object's docstring, or by any of its contained objects'
- docstrings.
-
- The optional parameter `module` is the module that contains
- the given object. If the module is not specified or is None, then
- the test finder will attempt to automatically determine the
- correct module. The object's module is used:
-
- - As a default namespace, if `globs` is not specified.
- - To prevent the DocTestFinder from extracting DocTests
- from objects that are imported from other modules.
- - To find the name of the file containing the object.
- - To help find the line number of the object within its
- file.
-
- Contained objects whose module does not match `module` are ignored.
-
- If `module` is False, no attempt to find the module will be made.
- This is obscure, of use mostly in tests: if `module` is False, or
- is None but cannot be found automatically, then all objects are
- considered to belong to the (non-existent) module, so all contained
- objects will (recursively) be searched for doctests.
-
- The globals for each DocTest is formed by combining `globs`
- and `extraglobs` (bindings in `extraglobs` override bindings
- in `globs`). A new copy of the globals dictionary is created
- for each DocTest. If `globs` is not specified, then it
- defaults to the module's `__dict__`, if specified, or {}
- otherwise. If `extraglobs` is not specified, then it defaults
- to {}.
-
- """
- # If name was not specified, then extract it from the object.
- if name is None:
- name = getattr(obj, '__name__', None)
- if name is None:
- raise ValueError("DocTestFinder.find: name must be given "
- "when obj.__name__ doesn't exist: %r" %
- (type(obj),))
-
- # Find the module that contains the given object (if obj is
- # a module, then module=obj.). Note: this may fail, in which
- # case module will be None.
- if module is False:
- module = None
- elif module is None:
- module = inspect.getmodule(obj)
-
- # Read the module's source code. This is used by
- # DocTestFinder._find_lineno to find the line number for a
- # given object's docstring.
- try:
- file = inspect.getsourcefile(obj) or inspect.getfile(obj)
- source_lines = linecache.getlines(file)
- if not source_lines:
- source_lines = None
- except TypeError:
- source_lines = None
-
- # Initialize globals, and merge in extraglobs.
- if globs is None:
- if module is None:
- globs = {}
- else:
- globs = module.__dict__.copy()
- else:
- globs = globs.copy()
- if extraglobs is not None:
- globs.update(extraglobs)
-
- # Recursively explore `obj`, extracting DocTests.
- tests = []
- self._find(tests, obj, name, module, source_lines, globs, {})
- return tests
-
- def _filter(self, obj, prefix, base):
- """
- Return true if the given object should not be examined.
- """
- return (self._namefilter is not None and
- self._namefilter(prefix, base))
-
- def _from_module(self, module, object):
- """
- Return true if the given object is defined in the given
- module.
- """
- if module is None:
- return True
- elif inspect.isfunction(object):
- return module.__dict__ is object.func_globals
- elif inspect.isclass(object):
- return module.__name__ == object.__module__
- elif inspect.getmodule(object) is not None:
- return module is inspect.getmodule(object)
- elif hasattr(object, '__module__'):
- return module.__name__ == object.__module__
- elif isinstance(object, property):
- return True # [XX] no way not be sure.
- else:
- raise ValueError("object must be a class or function")
-
- def _find(self, tests, obj, name, module, source_lines, globs, seen):
- """
- Find tests for the given object and any contained objects, and
- add them to `tests`.
- """
- if self._verbose:
- print 'Finding tests in %s' % name
-
- # If we've already processed this object, then ignore it.
- if id(obj) in seen:
- return
- seen[id(obj)] = 1
-
- # Find a test for this object, and add it to the list of tests.
- test = self._get_test(obj, name, module, globs, source_lines)
- if test is not None:
- tests.append(test)
-
- # Look for tests in a module's contained objects.
- if inspect.ismodule(obj) and self._recurse:
- for valname, val in obj.__dict__.items():
- # Check if this contained object should be ignored.
- if self._filter(val, name, valname):
- continue
- valname = '%s.%s' % (name, valname)
- # Recurse to functions & classes.
- if ((inspect.isfunction(val) or inspect.isclass(val)) and
- self._from_module(module, val)):
- self._find(tests, val, valname, module, source_lines,
- globs, seen)
-
- # Look for tests in a module's __test__ dictionary.
- if inspect.ismodule(obj) and self._recurse:
- for valname, val in getattr(obj, '__test__', {}).items():
- if not isinstance(valname, basestring):
- raise ValueError("DocTestFinder.find: __test__ keys "
- "must be strings: %r" %
- (type(valname),))
- if not (inspect.isfunction(val) or inspect.isclass(val) or
- inspect.ismethod(val) or inspect.ismodule(val) or
- isinstance(val, basestring)):
- raise ValueError("DocTestFinder.find: __test__ values "
- "must be strings, functions, methods, "
- "classes, or modules: %r" %
- (type(val),))
- valname = '%s.__test__.%s' % (name, valname)
- self._find(tests, val, valname, module, source_lines,
- globs, seen)
-
- # Look for tests in a class's contained objects.
- if inspect.isclass(obj) and self._recurse:
- for valname, val in obj.__dict__.items():
- # Check if this contained object should be ignored.
- if self._filter(val, name, valname):
- continue
- # Special handling for staticmethod/classmethod.
- if isinstance(val, staticmethod):
- val = getattr(obj, valname)
- if isinstance(val, classmethod):
- val = getattr(obj, valname).im_func
-
- # Recurse to methods, properties, and nested classes.
- if ((inspect.isfunction(val) or inspect.isclass(val) or
- isinstance(val, property)) and
- self._from_module(module, val)):
- valname = '%s.%s' % (name, valname)
- self._find(tests, val, valname, module, source_lines,
- globs, seen)
-
- def _get_test(self, obj, name, module, globs, source_lines):
- """
- Return a DocTest for the given object, if it defines a docstring;
- otherwise, return None.
- """
- # Extract the object's docstring. If it doesn't have one,
- # then return None (no test for this object).
- if isinstance(obj, basestring):
- docstring = obj
- else:
- try:
- if obj.__doc__ is None:
- docstring = ''
- else:
- docstring = obj.__doc__
- if not isinstance(docstring, basestring):
- docstring = str(docstring)
- except (TypeError, AttributeError):
- docstring = ''
-
- # Find the docstring's location in the file.
- lineno = self._find_lineno(obj, source_lines)
-
- # Don't bother if the docstring is empty.
- if self._exclude_empty and not docstring:
- return None
-
- # Return a DocTest for this object.
- if module is None:
- filename = None
- else:
- filename = getattr(module, '__file__', module.__name__)
- if filename[-4:] in (".pyc", ".pyo"):
- filename = filename[:-1]
- return self._parser.get_doctest(docstring, globs, name,
- filename, lineno)
-
- def _find_lineno(self, obj, source_lines):
- """
- Return a line number of the given object's docstring. Note:
- this method assumes that the object has a docstring.
- """
- lineno = None
-
- # Find the line number for modules.
- if inspect.ismodule(obj):
- lineno = 0
-
- # Find the line number for classes.
- # Note: this could be fooled if a class is defined multiple
- # times in a single file.
- if inspect.isclass(obj):
- if source_lines is None:
- return None
- pat = re.compile(r'^\s*class\s*%s\b' %
- getattr(obj, '__name__', '-'))
- for i, line in enumerate(source_lines):
- if pat.match(line):
- lineno = i
- break
-
- # Find the line number for functions & methods.
- if inspect.ismethod(obj): obj = obj.im_func
- if inspect.isfunction(obj): obj = obj.func_code
- if inspect.istraceback(obj): obj = obj.tb_frame
- if inspect.isframe(obj): obj = obj.f_code
- if inspect.iscode(obj):
- lineno = getattr(obj, 'co_firstlineno', None)-1
-
- # Find the line number where the docstring starts. Assume
- # that it's the first line that begins with a quote mark.
- # Note: this could be fooled by a multiline function
- # signature, where a continuation line begins with a quote
- # mark.
- if lineno is not None:
- if source_lines is None:
- return lineno+1
- pat = re.compile('(^|.*:)\s*\w*("|\')')
- for lineno in range(lineno, len(source_lines)):
- if pat.match(source_lines[lineno]):
- return lineno
-
- # We couldn't find the line number.
- return None
-
-######################################################################
-## 5. DocTest Runner
-######################################################################
-
-class DocTestRunner:
- """
- A class used to run DocTest test cases, and accumulate statistics.
- The `run` method is used to process a single DocTest case. It
- returns a tuple `(f, t)`, where `t` is the number of test cases
- tried, and `f` is the number of test cases that failed.
-
- >>> tests = DocTestFinder().find(_TestClass)
- >>> runner = DocTestRunner(verbose=False)
- >>> for test in tests:
- ... print runner.run(test)
- (0, 2)
- (0, 1)
- (0, 2)
- (0, 2)
-
- The `summarize` method prints a summary of all the test cases that
- have been run by the runner, and returns an aggregated `(f, t)`
- tuple:
-
- >>> runner.summarize(verbose=1)
- 4 items passed all tests:
- 2 tests in _TestClass
- 2 tests in _TestClass.__init__
- 2 tests in _TestClass.get
- 1 tests in _TestClass.square
- 7 tests in 4 items.
- 7 passed and 0 failed.
- Test passed.
- (0, 7)
-
- The aggregated number of tried examples and failed examples is
- also available via the `tries` and `failures` attributes:
-
- >>> runner.tries
- 7
- >>> runner.failures
- 0
-
- The comparison between expected outputs and actual outputs is done
- by an `OutputChecker`. This comparison may be customized with a
- number of option flags; see the documentation for `testmod` for
- more information. If the option flags are insufficient, then the
- comparison may also be customized by passing a subclass of
- `OutputChecker` to the constructor.
-
- The test runner's display output can be controlled in two ways.
- First, an output function (`out) can be passed to
- `TestRunner.run`; this function will be called with strings that
- should be displayed. It defaults to `sys.stdout.write`. If
- capturing the output is not sufficient, then the display output
- can be also customized by subclassing DocTestRunner, and
- overriding the methods `report_start`, `report_success`,
- `report_unexpected_exception`, and `report_failure`.
- """
- # This divider string is used to separate failure messages, and to
- # separate sections of the summary.
- DIVIDER = "*" * 70
-
- def __init__(self, checker=None, verbose=None, optionflags=0):
- """
- Create a new test runner.
-
- Optional keyword arg `checker` is the `OutputChecker` that
- should be used to compare the expected outputs and actual
- outputs of doctest examples.
-
- Optional keyword arg 'verbose' prints lots of stuff if true,
- only failures if false; by default, it's true iff '-v' is in
- sys.argv.
-
- Optional argument `optionflags` can be used to control how the
- test runner compares expected output to actual output, and how
- it displays failures. See the documentation for `testmod` for
- more information.
- """
- self._checker = checker or OutputChecker()
- if verbose is None:
- verbose = '-v' in sys.argv
- self._verbose = verbose
- self.optionflags = optionflags
- self.original_optionflags = optionflags
-
- # Keep track of the examples we've run.
- self.tries = 0
- self.failures = 0
- self._name2ft = {}
-
- # Create a fake output target for capturing doctest output.
- self._fakeout = _SpoofOut()
-
- #/////////////////////////////////////////////////////////////////
- # Reporting methods
- #/////////////////////////////////////////////////////////////////
-
- def report_start(self, out, test, example):
- """
- Report that the test runner is about to process the given
- example. (Only displays a message if verbose=True)
- """
- if self._verbose:
- if example.want:
- out('Trying:\n' + _indent(example.source) +
- 'Expecting:\n' + _indent(example.want))
- else:
- out('Trying:\n' + _indent(example.source) +
- 'Expecting nothing\n')
-
- def report_success(self, out, test, example, got):
- """
- Report that the given example ran successfully. (Only
- displays a message if verbose=True)
- """
- if self._verbose:
- out("ok\n")
-
- def report_failure(self, out, test, example, got):
- """
- Report that the given example failed.
- """
- out(self._failure_header(test, example) +
- self._checker.output_difference(example, got, self.optionflags))
-
- def report_unexpected_exception(self, out, test, example, exc_info):
- """
- Report that the given example raised an unexpected exception.
- """
- out(self._failure_header(test, example) +
- 'Exception raised:\n' + _indent(_exception_traceback(exc_info)))
-
- def _failure_header(self, test, example):
- out = [self.DIVIDER]
- if test.filename:
- if test.lineno is not None and example.lineno is not None:
- lineno = test.lineno + example.lineno + 1
- else:
- lineno = '?'
- out.append('File "%s", line %s, in %s' %
- (test.filename, lineno, test.name))
- else:
- out.append('Line %s, in %s' % (example.lineno+1, test.name))
- out.append('Failed example:')
- source = example.source
- out.append(_indent(source))
- return '\n'.join(out)
-
- #/////////////////////////////////////////////////////////////////
- # DocTest Running
- #/////////////////////////////////////////////////////////////////
-
- def __run(self, test, compileflags, out):
- """
- Run the examples in `test`. Write the outcome of each example
- with one of the `DocTestRunner.report_*` methods, using the
- writer function `out`. `compileflags` is the set of compiler
- flags that should be used to execute examples. Return a tuple
- `(f, t)`, where `t` is the number of examples tried, and `f`
- is the number of examples that failed. The examples are run
- in the namespace `test.globs`.
- """
- # Keep track of the number of failures and tries.
- failures = tries = 0
-
- # Save the option flags (since option directives can be used
- # to modify them).
- original_optionflags = self.optionflags
-
- SUCCESS, FAILURE, BOOM = range(3) # `outcome` state
-
- check = self._checker.check_output
-
- # Process each example.
- for examplenum, example in enumerate(test.examples):
-
- # If REPORT_ONLY_FIRST_FAILURE is set, then suppress
- # reporting after the first failure.
- quiet = (self.optionflags & REPORT_ONLY_FIRST_FAILURE and
- failures > 0)
-
- # Merge in the example's options.
- self.optionflags = original_optionflags
- if example.options:
- for (optionflag, val) in example.options.items():
- if val:
- self.optionflags |= optionflag
- else:
- self.optionflags &= ~optionflag
-
- # Record that we started this example.
- tries += 1
- if not quiet:
- self.report_start(out, test, example)
-
- # Use a special filename for compile(), so we can retrieve
- # the source code during interactive debugging (see
- # __patched_linecache_getlines).
- filename = '<doctest %s[%d]>' % (test.name, examplenum)
-
- # Run the example in the given context (globs), and record
- # any exception that gets raised. (But don't intercept
- # keyboard interrupts.)
- try:
- # Don't blink! This is where the user's code gets run.
- exec compile(example.source, filename, "single",
- compileflags, 1) in test.globs
- self.debugger.set_continue() # ==== Example Finished ====
- exception = None
- except KeyboardInterrupt:
- raise
- except:
- exception = sys.exc_info()
- self.debugger.set_continue() # ==== Example Finished ====
-
- got = self._fakeout.getvalue() # the actual output
- self._fakeout.truncate(0)
- outcome = FAILURE # guilty until proved innocent or insane
-
- # If the example executed without raising any exceptions,
- # verify its output.
- if exception is None:
- if check(example.want, got, self.optionflags):
- outcome = SUCCESS
-
- # The example raised an exception: check if it was expected.
- else:
- exc_info = sys.exc_info()
- exc_msg = traceback.format_exception_only(*exc_info[:2])[-1]
- if not quiet:
- got += _exception_traceback(exc_info)
-
- # If `example.exc_msg` is None, then we weren't expecting
- # an exception.
- if example.exc_msg is None:
- outcome = BOOM
-
- # We expected an exception: see whether it matches.
- elif check(example.exc_msg, exc_msg, self.optionflags):
- outcome = SUCCESS
-
- # Another chance if they didn't care about the detail.
- elif self.optionflags & IGNORE_EXCEPTION_DETAIL:
- m1 = re.match(r'[^:]*:', example.exc_msg)
- m2 = re.match(r'[^:]*:', exc_msg)
- if m1 and m2 and check(m1.group(0), m2.group(0),
- self.optionflags):
- outcome = SUCCESS
-
- # Report the outcome.
- if outcome is SUCCESS:
- if not quiet:
- self.report_success(out, test, example, got)
- elif outcome is FAILURE:
- if not quiet:
- self.report_failure(out, test, example, got)
- failures += 1
- elif outcome is BOOM:
- if not quiet:
- self.report_unexpected_exception(out, test, example,
- exc_info)
- failures += 1
- else:
- assert False, ("unknown outcome", outcome)
-
- # Restore the option flags (in case they were modified)
- self.optionflags = original_optionflags
-
- # Record and return the number of failures and tries.
- self.__record_outcome(test, failures, tries)
- return failures, tries
-
- def __record_outcome(self, test, f, t):
- """
- Record the fact that the given DocTest (`test`) generated `f`
- failures out of `t` tried examples.
- """
- f2, t2 = self._name2ft.get(test.name, (0,0))
- self._name2ft[test.name] = (f+f2, t+t2)
- self.failures += f
- self.tries += t
-
- __LINECACHE_FILENAME_RE = re.compile(r'<doctest '
- r'(?P<name>[\w\.]+)'
- r'\[(?P<examplenum>\d+)\]>$')
- def __patched_linecache_getlines(self, filename):
- m = self.__LINECACHE_FILENAME_RE.match(filename)
- if m and m.group('name') == self.test.name:
- example = self.test.examples[int(m.group('examplenum'))]
- return example.source.splitlines(True)
- else:
- return self.save_linecache_getlines(filename)
-
- def run(self, test, compileflags=None, out=None, clear_globs=True):
- """
- Run the examples in `test`, and display the results using the
- writer function `out`.
-
- The examples are run in the namespace `test.globs`. If
- `clear_globs` is true (the default), then this namespace will
- be cleared after the test runs, to help with garbage
- collection. If you would like to examine the namespace after
- the test completes, then use `clear_globs=False`.
-
- `compileflags` gives the set of flags that should be used by
- the Python compiler when running the examples. If not
- specified, then it will default to the set of future-import
- flags that apply to `globs`.
-
- The output of each example is checked using
- `DocTestRunner.check_output`, and the results are formatted by
- the `DocTestRunner.report_*` methods.
- """
- self.test = test
-
- if compileflags is None:
- compileflags = _extract_future_flags(test.globs)
-
- save_stdout = sys.stdout
- if out is None:
- out = save_stdout.write
- sys.stdout = self._fakeout
-
- # Patch pdb.set_trace to restore sys.stdout during interactive
- # debugging (so it's not still redirected to self._fakeout).
- # Note that the interactive output will go to *our*
- # save_stdout, even if that's not the real sys.stdout; this
- # allows us to write test cases for the set_trace behavior.
- save_set_trace = pdb.set_trace
- self.debugger = _OutputRedirectingPdb(save_stdout)
- self.debugger.reset()
- pdb.set_trace = self.debugger.set_trace
-
- # Patch linecache.getlines, so we can see the example's source
- # when we're inside the debugger.
- self.save_linecache_getlines = linecache.getlines
- linecache.getlines = self.__patched_linecache_getlines
-
- try:
- return self.__run(test, compileflags, out)
- finally:
- sys.stdout = save_stdout
- pdb.set_trace = save_set_trace
- linecache.getlines = self.save_linecache_getlines
- if clear_globs:
- test.globs.clear()
-
- #/////////////////////////////////////////////////////////////////
- # Summarization
- #/////////////////////////////////////////////////////////////////
- def summarize(self, verbose=None):
- """
- Print a summary of all the test cases that have been run by
- this DocTestRunner, and return a tuple `(f, t)`, where `f` is
- the total number of failed examples, and `t` is the total
- number of tried examples.
-
- The optional `verbose` argument controls how detailed the
- summary is. If the verbosity is not specified, then the
- DocTestRunner's verbosity is used.
- """
- if verbose is None:
- verbose = self._verbose
- notests = []
- passed = []
- failed = []
- totalt = totalf = 0
- for x in self._name2ft.items():
- name, (f, t) = x
- assert f <= t
- totalt += t
- totalf += f
- if t == 0:
- notests.append(name)
- elif f == 0:
- passed.append( (name, t) )
- else:
- failed.append(x)
- if verbose:
- if notests:
- print len(notests), "items had no tests:"
- notests.sort()
- for thing in notests:
- print " ", thing
- if passed:
- print len(passed), "items passed all tests:"
- passed.sort()
- for thing, count in passed:
- print " %3d tests in %s" % (count, thing)
- if failed:
- print self.DIVIDER
- print len(failed), "items had failures:"
- failed.sort()
- for thing, (f, t) in failed:
- print " %3d of %3d in %s" % (f, t, thing)
- if verbose:
- print totalt, "tests in", len(self._name2ft), "items."
- print totalt - totalf, "passed and", totalf, "failed."
- if totalf:
- print "***Test Failed***", totalf, "failures."
- elif verbose:
- print "Test passed."
- return totalf, totalt
-
- #/////////////////////////////////////////////////////////////////
- # Backward compatibility cruft to maintain doctest.master.
- #/////////////////////////////////////////////////////////////////
- def merge(self, other):
- d = self._name2ft
- for name, (f, t) in other._name2ft.items():
- if name in d:
- print "*** DocTestRunner.merge: '" + name + "' in both" \
- " testers; summing outcomes."
- f2, t2 = d[name]
- f = f + f2
- t = t + t2
- d[name] = f, t
-
-class OutputChecker:
- """
- A class used to check the whether the actual output from a doctest
- example matches the expected output. `OutputChecker` defines two
- methods: `check_output`, which compares a given pair of outputs,
- and returns true if they match; and `output_difference`, which
- returns a string describing the differences between two outputs.
- """
- def check_output(self, want, got, optionflags):
- """
- Return True iff the actual output from an example (`got`)
- matches the expected output (`want`). These strings are
- always considered to match if they are identical; but
- depending on what option flags the test runner is using,
- several non-exact match types are also possible. See the
- documentation for `TestRunner` for more information about
- option flags.
- """
- # Handle the common case first, for efficiency:
- # if they're string-identical, always return true.
- if got == want:
- return True
-
- # The values True and False replaced 1 and 0 as the return
- # value for boolean comparisons in Python 2.3.
- if not (optionflags & DONT_ACCEPT_TRUE_FOR_1):
- if (got,want) == ("True\n", "1\n"):
- return True
- if (got,want) == ("False\n", "0\n"):
- return True
-
- # <BLANKLINE> can be used as a special sequence to signify a
- # blank line, unless the DONT_ACCEPT_BLANKLINE flag is used.
- if not (optionflags & DONT_ACCEPT_BLANKLINE):
- # Replace <BLANKLINE> in want with a blank line.
- want = re.sub('(?m)^%s\s*?$' % re.escape(BLANKLINE_MARKER),
- '', want)
- # If a line in got contains only spaces, then remove the
- # spaces.
- got = re.sub('(?m)^\s*?$', '', got)
- if got == want:
- return True
-
- # This flag causes doctest to ignore any differences in the
- # contents of whitespace strings. Note that this can be used
- # in conjunction with the ELLIPSIS flag.
- if optionflags & NORMALIZE_WHITESPACE:
- got = ' '.join(got.split())
- want = ' '.join(want.split())
- if got == want:
- return True
-
- # The ELLIPSIS flag says to let the sequence "..." in `want`
- # match any substring in `got`.
- if optionflags & ELLIPSIS:
- if _ellipsis_match(want, got):
- return True
-
- # We didn't find any match; return false.
- return False
-
- # Should we do a fancy diff?
- def _do_a_fancy_diff(self, want, got, optionflags):
- # Not unless they asked for a fancy diff.
- if not optionflags & (REPORT_UDIFF |
- REPORT_CDIFF |
- REPORT_NDIFF):
- return False
-
- # If expected output uses ellipsis, a meaningful fancy diff is
- # too hard ... or maybe not. In two real-life failures Tim saw,
- # a diff was a major help anyway, so this is commented out.
- # [todo] _ellipsis_match() knows which pieces do and don't match,
- # and could be the basis for a kick-ass diff in this case.
- ##if optionflags & ELLIPSIS and ELLIPSIS_MARKER in want:
- ## return False
-
- # ndiff does intraline difference marking, so can be useful even
- # for 1-line differences.
- if optionflags & REPORT_NDIFF:
- return True
-
- # The other diff types need at least a few lines to be helpful.
- return want.count('\n') > 2 and got.count('\n') > 2
-
- def output_difference(self, example, got, optionflags):
- """
- Return a string describing the differences between the
- expected output for a given example (`example`) and the actual
- output (`got`). `optionflags` is the set of option flags used
- to compare `want` and `got`.
- """
- want = example.want
- # If <BLANKLINE>s are being used, then replace blank lines
- # with <BLANKLINE> in the actual output string.
- if not (optionflags & DONT_ACCEPT_BLANKLINE):
- got = re.sub('(?m)^[ ]*(?=\n)', BLANKLINE_MARKER, got)
-
- # Check if we should use diff.
- if self._do_a_fancy_diff(want, got, optionflags):
- # Split want & got into lines.
- want_lines = want.splitlines(True) # True == keep line ends
- got_lines = got.splitlines(True)
- # Use difflib to find their differences.
- if optionflags & REPORT_UDIFF:
- diff = difflib.unified_diff(want_lines, got_lines, n=2)
- diff = list(diff)[2:] # strip the diff header
- kind = 'unified diff with -expected +actual'
- elif optionflags & REPORT_CDIFF:
- diff = difflib.context_diff(want_lines, got_lines, n=2)
- diff = list(diff)[2:] # strip the diff header
- kind = 'context diff with expected followed by actual'
- elif optionflags & REPORT_NDIFF:
- engine = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK)
- diff = list(engine.compare(want_lines, got_lines))
- kind = 'ndiff with -expected +actual'
- else:
- assert 0, 'Bad diff option'
- # Remove trailing whitespace on diff output.
- diff = [line.rstrip() + '\n' for line in diff]
- return 'Differences (%s):\n' % kind + _indent(''.join(diff))
-
- # If we're not using diff, then simply list the expected
- # output followed by the actual output.
- if want and got:
- return 'Expected:\n%sGot:\n%s' % (_indent(want), _indent(got))
- elif want:
- return 'Expected:\n%sGot nothing\n' % _indent(want)
- elif got:
- return 'Expected nothing\nGot:\n%s' % _indent(got)
- else:
- return 'Expected nothing\nGot nothing\n'
-
-class DocTestFailure(Exception):
- """A DocTest example has failed in debugging mode.
-
- The exception instance has variables:
-
- - test: the DocTest object being run
-
- - excample: the Example object that failed
-
- - got: the actual output
- """
- def __init__(self, test, example, got):
- self.test = test
- self.example = example
- self.got = got
-
- def __str__(self):
- return str(self.test)
-
-class UnexpectedException(Exception):
- """A DocTest example has encountered an unexpected exception
-
- The exception instance has variables:
-
- - test: the DocTest object being run
-
- - excample: the Example object that failed
-
- - exc_info: the exception info
- """
- def __init__(self, test, example, exc_info):
- self.test = test
- self.example = example
- self.exc_info = exc_info
-
- def __str__(self):
- return str(self.test)
-
-class DebugRunner(DocTestRunner):
- r"""Run doc tests but raise an exception as soon as there is a failure.
-
- If an unexpected exception occurs, an UnexpectedException is raised.
- It contains the test, the example, and the original exception:
-
- >>> runner = DebugRunner(verbose=False)
- >>> test = DocTestParser().get_doctest('>>> raise KeyError\n42',
- ... {}, 'foo', 'foo.py', 0)
- >>> try:
- ... runner.run(test)
- ... except UnexpectedException, failure:
- ... pass
-
- >>> failure.test is test
- True
-
- >>> failure.example.want
- '42\n'
-
- >>> exc_info = failure.exc_info
- >>> raise exc_info[0], exc_info[1], exc_info[2]
- Traceback (most recent call last):
- ...
- KeyError
-
- We wrap the original exception to give the calling application
- access to the test and example information.
-
- If the output doesn't match, then a DocTestFailure is raised:
-
- >>> test = DocTestParser().get_doctest('''
- ... >>> x = 1
- ... >>> x
- ... 2
- ... ''', {}, 'foo', 'foo.py', 0)
-
- >>> try:
- ... runner.run(test)
- ... except DocTestFailure, failure:
- ... pass
-
- DocTestFailure objects provide access to the test:
-
- >>> failure.test is test
- True
-
- As well as to the example:
-
- >>> failure.example.want
- '2\n'
-
- and the actual output:
-
- >>> failure.got
- '1\n'
-
- If a failure or error occurs, the globals are left intact:
-
- >>> del test.globs['__builtins__']
- >>> test.globs
- {'x': 1}
-
- >>> test = DocTestParser().get_doctest('''
- ... >>> x = 2
- ... >>> raise KeyError
- ... ''', {}, 'foo', 'foo.py', 0)
-
- >>> runner.run(test)
- Traceback (most recent call last):
- ...
- UnexpectedException: <DocTest foo from foo.py:0 (2 examples)>
-
- >>> del test.globs['__builtins__']
- >>> test.globs
- {'x': 2}
-
- But the globals are cleared if there is no error:
-
- >>> test = DocTestParser().get_doctest('''
- ... >>> x = 2
- ... ''', {}, 'foo', 'foo.py', 0)
-
- >>> runner.run(test)
- (0, 1)
-
- >>> test.globs
- {}
-
- """
-
- def run(self, test, compileflags=None, out=None, clear_globs=True):
- r = DocTestRunner.run(self, test, compileflags, out, False)
- if clear_globs:
- test.globs.clear()
- return r
-
- def report_unexpected_exception(self, out, test, example, exc_info):
- raise UnexpectedException(test, example, exc_info)
-
- def report_failure(self, out, test, example, got):
- raise DocTestFailure(test, example, got)
-
-######################################################################
-## 6. Test Functions
-######################################################################
-# These should be backwards compatible.
-
-# For backward compatibility, a global instance of a DocTestRunner
-# class, updated by testmod.
-master = None
-
-def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
- report=True, optionflags=0, extraglobs=None,
- raise_on_error=False, exclude_empty=False):
- """m=None, name=None, globs=None, verbose=None, isprivate=None,
- report=True, optionflags=0, extraglobs=None, raise_on_error=False,
- exclude_empty=False
-
- Test examples in docstrings in functions and classes reachable
- from module m (or the current module if m is not supplied), starting
- with m.__doc__. Unless isprivate is specified, private names
- are not skipped.
-
- Also test examples reachable from dict m.__test__ if it exists and is
- not None. m.__test__ maps names to functions, classes and strings;
- function and class docstrings are tested even if the name is private;
- strings are tested directly, as if they were docstrings.
-
- Return (#failures, #tests).
-
- See doctest.__doc__ for an overview.
-
- Optional keyword arg "name" gives the name of the module; by default
- use m.__name__.
-
- Optional keyword arg "globs" gives a dict to be used as the globals
- when executing examples; by default, use m.__dict__. A copy of this
- dict is actually used for each docstring, so that each docstring's
- examples start with a clean slate.
-
- Optional keyword arg "extraglobs" gives a dictionary that should be
- merged into the globals that are used to execute examples. By
- default, no extra globals are used. This is new in 2.4.
-
- Optional keyword arg "verbose" prints lots of stuff if true, prints
- only failures if false; by default, it's true iff "-v" is in sys.argv.
-
- Optional keyword arg "report" prints a summary at the end when true,
- else prints nothing at the end. In verbose mode, the summary is
- detailed, else very brief (in fact, empty if all tests passed).
-
- Optional keyword arg "optionflags" or's together module constants,
- and defaults to 0. This is new in 2.3. Possible values (see the
- docs for details):
-
- DONT_ACCEPT_TRUE_FOR_1
- DONT_ACCEPT_BLANKLINE
- NORMALIZE_WHITESPACE
- ELLIPSIS
- IGNORE_EXCEPTION_DETAIL
- REPORT_UDIFF
- REPORT_CDIFF
- REPORT_NDIFF
- REPORT_ONLY_FIRST_FAILURE
-
- Optional keyword arg "raise_on_error" raises an exception on the
- first unexpected exception or failure. This allows failures to be
- post-mortem debugged.
-
- Deprecated in Python 2.4:
- Optional keyword arg "isprivate" specifies a function used to
- determine whether a name is private. The default function is
- treat all functions as public. Optionally, "isprivate" can be
- set to doctest.is_private to skip over functions marked as private
- using the underscore naming convention; see its docs for details.
-
- Advanced tomfoolery: testmod runs methods of a local instance of
- class doctest.Tester, then merges the results into (or creates)
- global Tester instance doctest.master. Methods of doctest.master
- can be called directly too, if you want to do something unusual.
- Passing report=0 to testmod is especially useful then, to delay
- displaying a summary. Invoke doctest.master.summarize(verbose)
- when you're done fiddling.
- """
- global master
-
- if isprivate is not None:
- warnings.warn("the isprivate argument is deprecated; "
- "examine DocTestFinder.find() lists instead",
- DeprecationWarning)
-
- # If no module was given, then use __main__.
- if m is None:
- # DWA - m will still be None if this wasn't invoked from the command
- # line, in which case the following TypeError is about as good an error
- # as we should expect
- m = sys.modules.get('__main__')
-
- # Check that we were actually given a module.
- if not inspect.ismodule(m):
- raise TypeError("testmod: module required; %r" % (m,))
-
- # If no name was given, then use the module's name.
- if name is None:
- name = m.__name__
-
- # Find, parse, and run all tests in the given module.
- finder = DocTestFinder(_namefilter=isprivate, exclude_empty=exclude_empty)
-
- if raise_on_error:
- runner = DebugRunner(verbose=verbose, optionflags=optionflags)
- else:
- runner = DocTestRunner(verbose=verbose, optionflags=optionflags)
-
- for test in finder.find(m, name, globs=globs, extraglobs=extraglobs):
- runner.run(test)
-
- if report:
- runner.summarize()
-
- if master is None:
- master = runner
- else:
- master.merge(runner)
-
- return runner.failures, runner.tries
-
-def testfile(filename, module_relative=True, name=None, package=None,
- globs=None, verbose=None, report=True, optionflags=0,
- extraglobs=None, raise_on_error=False, parser=DocTestParser()):
- """
- Test examples in the given file. Return (#failures, #tests).
-
- Optional keyword arg "module_relative" specifies how filenames
- should be interpreted:
-
- - If "module_relative" is True (the default), then "filename"
- specifies a module-relative path. By default, this path is
- relative to the calling module's directory; but if the
- "package" argument is specified, then it is relative to that
- package. To ensure os-independence, "filename" should use
- "/" characters to separate path segments, and should not
- be an absolute path (i.e., it may not begin with "/").
-
- - If "module_relative" is False, then "filename" specifies an
- os-specific path. The path may be absolute or relative (to
- the current working directory).
-
- Optional keyword arg "name" gives the name of the test; by default
- use the file's basename.
-
- Optional keyword argument "package" is a Python package or the
- name of a Python package whose directory should be used as the
- base directory for a module relative filename. If no package is
- specified, then the calling module's directory is used as the base
- directory for module relative filenames. It is an error to
- specify "package" if "module_relative" is False.
-
- Optional keyword arg "globs" gives a dict to be used as the globals
- when executing examples; by default, use {}. A copy of this dict
- is actually used for each docstring, so that each docstring's
- examples start with a clean slate.
-
- Optional keyword arg "extraglobs" gives a dictionary that should be
- merged into the globals that are used to execute examples. By
- default, no extra globals are used.
-
- Optional keyword arg "verbose" prints lots of stuff if true, prints
- only failures if false; by default, it's true iff "-v" is in sys.argv.
-
- Optional keyword arg "report" prints a summary at the end when true,
- else prints nothing at the end. In verbose mode, the summary is
- detailed, else very brief (in fact, empty if all tests passed).
-
- Optional keyword arg "optionflags" or's together module constants,
- and defaults to 0. Possible values (see the docs for details):
-
- DONT_ACCEPT_TRUE_FOR_1
- DONT_ACCEPT_BLANKLINE
- NORMALIZE_WHITESPACE
- ELLIPSIS
- IGNORE_EXCEPTION_DETAIL
- REPORT_UDIFF
- REPORT_CDIFF
- REPORT_NDIFF
- REPORT_ONLY_FIRST_FAILURE
-
- Optional keyword arg "raise_on_error" raises an exception on the
- first unexpected exception or failure. This allows failures to be
- post-mortem debugged.
-
- Optional keyword arg "parser" specifies a DocTestParser (or
- subclass) that should be used to extract tests from the files.
-
- Advanced tomfoolery: testmod runs methods of a local instance of
- class doctest.Tester, then merges the results into (or creates)
- global Tester instance doctest.master. Methods of doctest.master
- can be called directly too, if you want to do something unusual.
- Passing report=0 to testmod is especially useful then, to delay
- displaying a summary. Invoke doctest.master.summarize(verbose)
- when you're done fiddling.
- """
- global master
-
- if package and not module_relative:
- raise ValueError("Package may only be specified for module-"
- "relative paths.")
-
- # Relativize the path
- if module_relative:
- package = _normalize_module(package)
- filename = _module_relative_path(package, filename)
-
- # If no name was given, then use the file's name.
- if name is None:
- name = os.path.basename(filename)
-
- # Assemble the globals.
- if globs is None:
- globs = {}
- else:
- globs = globs.copy()
- if extraglobs is not None:
- globs.update(extraglobs)
-
- if raise_on_error:
- runner = DebugRunner(verbose=verbose, optionflags=optionflags)
- else:
- runner = DocTestRunner(verbose=verbose, optionflags=optionflags)
-
- # Read the file, convert it to a test, and run it.
- s = open(filename).read()
- test = parser.get_doctest(s, globs, name, filename, 0)
- runner.run(test)
-
- if report:
- runner.summarize()
-
- if master is None:
- master = runner
- else:
- master.merge(runner)
-
- return runner.failures, runner.tries
-
-def run_docstring_examples(f, globs, verbose=False, name="NoName",
- compileflags=None, optionflags=0):
- """
- Test examples in the given object's docstring (`f`), using `globs`
- as globals. Optional argument `name` is used in failure messages.
- If the optional argument `verbose` is true, then generate output
- even if there are no failures.
-
- `compileflags` gives the set of flags that should be used by the
- Python compiler when running the examples. If not specified, then
- it will default to the set of future-import flags that apply to
- `globs`.
-
- Optional keyword arg `optionflags` specifies options for the
- testing and output. See the documentation for `testmod` for more
- information.
- """
- # Find, parse, and run all tests in the given module.
- finder = DocTestFinder(verbose=verbose, recurse=False)
- runner = DocTestRunner(verbose=verbose, optionflags=optionflags)
- for test in finder.find(f, name, globs=globs):
- runner.run(test, compileflags=compileflags)
-
-######################################################################
-## 7. Tester
-######################################################################
-# This is provided only for backwards compatibility. It's not
-# actually used in any way.
-
-class Tester:
- def __init__(self, mod=None, globs=None, verbose=None,
- isprivate=None, optionflags=0):
-
- warnings.warn("class Tester is deprecated; "
- "use class doctest.DocTestRunner instead",
- DeprecationWarning, stacklevel=2)
- if mod is None and globs is None:
- raise TypeError("Tester.__init__: must specify mod or globs")
- if mod is not None and not inspect.ismodule(mod):
- raise TypeError("Tester.__init__: mod must be a module; %r" %
- (mod,))
- if globs is None:
- globs = mod.__dict__
- self.globs = globs
-
- self.verbose = verbose
- self.isprivate = isprivate
- self.optionflags = optionflags
- self.testfinder = DocTestFinder(_namefilter=isprivate)
- self.testrunner = DocTestRunner(verbose=verbose,
- optionflags=optionflags)
-
- def runstring(self, s, name):
- test = DocTestParser().get_doctest(s, self.globs, name, None, None)
- if self.verbose:
- print "Running string", name
- (f,t) = self.testrunner.run(test)
- if self.verbose:
- print f, "of", t, "examples failed in string", name
- return (f,t)
-
- def rundoc(self, object, name=None, module=None):
- f = t = 0
- tests = self.testfinder.find(object, name, module=module,
- globs=self.globs)
- for test in tests:
- (f2, t2) = self.testrunner.run(test)
- (f,t) = (f+f2, t+t2)
- return (f,t)
-
- def rundict(self, d, name, module=None):
- import new
- m = new.module(name)
- m.__dict__.update(d)
- if module is None:
- module = False
- return self.rundoc(m, name, module)
-
- def run__test__(self, d, name):
- import new
- m = new.module(name)
- m.__test__ = d
- return self.rundoc(m, name)
-
- def summarize(self, verbose=None):
- return self.testrunner.summarize(verbose)
-
- def merge(self, other):
- self.testrunner.merge(other.testrunner)
-
-######################################################################
-## 8. Unittest Support
-######################################################################
-
-_unittest_reportflags = 0
-
-def set_unittest_reportflags(flags):
- """Sets the unittest option flags.
-
- The old flag is returned so that a runner could restore the old
- value if it wished to:
-
- >>> old = _unittest_reportflags
- >>> set_unittest_reportflags(REPORT_NDIFF |
- ... REPORT_ONLY_FIRST_FAILURE) == old
- True
-
- >>> import doctest
- >>> doctest._unittest_reportflags == (REPORT_NDIFF |
- ... REPORT_ONLY_FIRST_FAILURE)
- True
-
- Only reporting flags can be set:
-
- >>> set_unittest_reportflags(ELLIPSIS)
- Traceback (most recent call last):
- ...
- ValueError: ('Only reporting flags allowed', 8)
-
- >>> set_unittest_reportflags(old) == (REPORT_NDIFF |
- ... REPORT_ONLY_FIRST_FAILURE)
- True
- """
- global _unittest_reportflags
-
- if (flags & REPORTING_FLAGS) != flags:
- raise ValueError("Only reporting flags allowed", flags)
- old = _unittest_reportflags
- _unittest_reportflags = flags
- return old
-
-
-class DocTestCase(unittest.TestCase):
-
- def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
- checker=None):
-
- unittest.TestCase.__init__(self)
- self._dt_optionflags = optionflags
- self._dt_checker = checker
- self._dt_test = test
- self._dt_setUp = setUp
- self._dt_tearDown = tearDown
-
- def setUp(self):
- test = self._dt_test
-
- if self._dt_setUp is not None:
- self._dt_setUp(test)
-
- def tearDown(self):
- test = self._dt_test
-
- if self._dt_tearDown is not None:
- self._dt_tearDown(test)
-
- test.globs.clear()
-
- def runTest(self):
- test = self._dt_test
- old = sys.stdout
- new = StringIO()
- optionflags = self._dt_optionflags
-
- if not (optionflags & REPORTING_FLAGS):
- # The option flags don't include any reporting flags,
- # so add the default reporting flags
- optionflags |= _unittest_reportflags
-
- runner = DocTestRunner(optionflags=optionflags,
- checker=self._dt_checker, verbose=False)
-
- try:
- runner.DIVIDER = "-"*70
- failures, tries = runner.run(
- test, out=new.write, clear_globs=False)
- finally:
- sys.stdout = old
-
- if failures:
- raise self.failureException(self.format_failure(new.getvalue()))
-
- def format_failure(self, err):
- test = self._dt_test
- if test.lineno is None:
- lineno = 'unknown line number'
- else:
- lineno = '%s' % test.lineno
- lname = '.'.join(test.name.split('.')[-1:])
- return ('Failed doctest test for %s\n'
- ' File "%s", line %s, in %s\n\n%s'
- % (test.name, test.filename, lineno, lname, err)
- )
-
- def debug(self):
- r"""Run the test case without results and without catching exceptions
-
- The unit test framework includes a debug method on test cases
- and test suites to support post-mortem debugging. The test code
- is run in such a way that errors are not caught. This way a
- caller can catch the errors and initiate post-mortem debugging.
-
- The DocTestCase provides a debug method that raises
- UnexpectedException errors if there is an unexepcted
- exception:
-
- >>> test = DocTestParser().get_doctest('>>> raise KeyError\n42',
- ... {}, 'foo', 'foo.py', 0)
- >>> case = DocTestCase(test)
- >>> try:
- ... case.debug()
- ... except UnexpectedException, failure:
- ... pass
-
- The UnexpectedException contains the test, the example, and
- the original exception:
-
- >>> failure.test is test
- True
-
- >>> failure.example.want
- '42\n'
-
- >>> exc_info = failure.exc_info
- >>> raise exc_info[0], exc_info[1], exc_info[2]
- Traceback (most recent call last):
- ...
- KeyError
-
- If the output doesn't match, then a DocTestFailure is raised:
-
- >>> test = DocTestParser().get_doctest('''
- ... >>> x = 1
- ... >>> x
- ... 2
- ... ''', {}, 'foo', 'foo.py', 0)
- >>> case = DocTestCase(test)
-
- >>> try:
- ... case.debug()
- ... except DocTestFailure, failure:
- ... pass
-
- DocTestFailure objects provide access to the test:
-
- >>> failure.test is test
- True
-
- As well as to the example:
-
- >>> failure.example.want
- '2\n'
-
- and the actual output:
-
- >>> failure.got
- '1\n'
-
- """
-
- self.setUp()
- runner = DebugRunner(optionflags=self._dt_optionflags,
- checker=self._dt_checker, verbose=False)
- runner.run(self._dt_test)
- self.tearDown()
-
- def id(self):
- return self._dt_test.name
-
- def __repr__(self):
- name = self._dt_test.name.split('.')
- return "%s (%s)" % (name[-1], '.'.join(name[:-1]))
-
- __str__ = __repr__
-
- def shortDescription(self):
- return "Doctest: " + self._dt_test.name
-
-def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None,
- **options):
- """
- Convert doctest tests for a module to a unittest test suite.
-
- This converts each documentation string in a module that
- contains doctest tests to a unittest test case. If any of the
- tests in a doc string fail, then the test case fails. An exception
- is raised showing the name of the file containing the test and a
- (sometimes approximate) line number.
-
- The `module` argument provides the module to be tested. The argument
- can be either a module or a module name.
-
- If no argument is given, the calling module is used.
-
- A number of options may be provided as keyword arguments:
-
- setUp
- A set-up function. This is called before running the
- tests in each file. The setUp function will be passed a DocTest
- object. The setUp function can access the test globals as the
- globs attribute of the test passed.
-
- tearDown
- A tear-down function. This is called after running the
- tests in each file. The tearDown function will be passed a DocTest
- object. The tearDown function can access the test globals as the
- globs attribute of the test passed.
-
- globs
- A dictionary containing initial global variables for the tests.
-
- optionflags
- A set of doctest option flags expressed as an integer.
- """
-
- if test_finder is None:
- test_finder = DocTestFinder()
-
- module = _normalize_module(module)
- tests = test_finder.find(module, globs=globs, extraglobs=extraglobs)
- if globs is None:
- globs = module.__dict__
- if not tests:
- # Why do we want to do this? Because it reveals a bug that might
- # otherwise be hidden.
- raise ValueError(module, "has no tests")
-
- tests.sort()
- suite = unittest.TestSuite()
- for test in tests:
- if len(test.examples) == 0:
- continue
- if not test.filename:
- filename = module.__file__
- if filename[-4:] in (".pyc", ".pyo"):
- filename = filename[:-1]
- test.filename = filename
- suite.addTest(DocTestCase(test, **options))
-
- return suite
-
-class DocFileCase(DocTestCase):
-
- def id(self):
- return '_'.join(self._dt_test.name.split('.'))
-
- def __repr__(self):
- return self._dt_test.filename
- __str__ = __repr__
-
- def format_failure(self, err):
- return ('Failed doctest test for %s\n File "%s", line 0\n\n%s'
- % (self._dt_test.name, self._dt_test.filename, err)
- )
-
-def DocFileTest(path, module_relative=True, package=None,
- globs=None, parser=DocTestParser(), **options):
- if globs is None:
- globs = {}
-
- if package and not module_relative:
- raise ValueError("Package may only be specified for module-"
- "relative paths.")
-
- # Relativize the path.
- if module_relative:
- package = _normalize_module(package)
- path = _module_relative_path(package, path)
-
- # Find the file and read it.
- name = os.path.basename(path)
- doc = open(path).read()
-
- # Convert it to a test, and wrap it in a DocFileCase.
- test = parser.get_doctest(doc, globs, name, path, 0)
- return DocFileCase(test, **options)
-
-def DocFileSuite(*paths, **kw):
- """A unittest suite for one or more doctest files.
-
- The path to each doctest file is given as a string; the
- interpretation of that string depends on the keyword argument
- "module_relative".
-
- A number of options may be provided as keyword arguments:
-
- module_relative
- If "module_relative" is True, then the given file paths are
- interpreted as os-independent module-relative paths. By
- default, these paths are relative to the calling module's
- directory; but if the "package" argument is specified, then
- they are relative to that package. To ensure os-independence,
- "filename" should use "/" characters to separate path
- segments, and may not be an absolute path (i.e., it may not
- begin with "/").
-
- If "module_relative" is False, then the given file paths are
- interpreted as os-specific paths. These paths may be absolute
- or relative (to the current working directory).
-
- package
- A Python package or the name of a Python package whose directory
- should be used as the base directory for module relative paths.
- If "package" is not specified, then the calling module's
- directory is used as the base directory for module relative
- filenames. It is an error to specify "package" if
- "module_relative" is False.
-
- setUp
- A set-up function. This is called before running the
- tests in each file. The setUp function will be passed a DocTest
- object. The setUp function can access the test globals as the
- globs attribute of the test passed.
-
- tearDown
- A tear-down function. This is called after running the
- tests in each file. The tearDown function will be passed a DocTest
- object. The tearDown function can access the test globals as the
- globs attribute of the test passed.
-
- globs
- A dictionary containing initial global variables for the tests.
-
- optionflags
- A set of doctest option flags expressed as an integer.
-
- parser
- A DocTestParser (or subclass) that should be used to extract
- tests from the files.
- """
- suite = unittest.TestSuite()
-
- # We do this here so that _normalize_module is called at the right
- # level. If it were called in DocFileTest, then this function
- # would be the caller and we might guess the package incorrectly.
- if kw.get('module_relative', True):
- kw['package'] = _normalize_module(kw.get('package'))
-
- for path in paths:
- suite.addTest(DocFileTest(path, **kw))
-
- return suite
-
-######################################################################
-## 9. Debugging Support
-######################################################################
-
-def script_from_examples(s):
- r"""Extract script from text with examples.
-
- Converts text with examples to a Python script. Example input is
- converted to regular code. Example output and all other words
- are converted to comments:
-
- >>> text = '''
- ... Here are examples of simple math.
- ...
- ... Python has super accurate integer addition
- ...
- ... >>> 2 + 2
- ... 5
- ...
- ... And very friendly error messages:
- ...
- ... >>> 1/0
- ... To Infinity
- ... And
- ... Beyond
- ...
- ... You can use logic if you want:
- ...
- ... >>> if 0:
- ... ... blah
- ... ... blah
- ... ...
- ...
- ... Ho hum
- ... '''
-
- >>> print script_from_examples(text)
- # Here are examples of simple math.
- #
- # Python has super accurate integer addition
- #
- 2 + 2
- # Expected:
- ## 5
- #
- # And very friendly error messages:
- #
- 1/0
- # Expected:
- ## To Infinity
- ## And
- ## Beyond
- #
- # You can use logic if you want:
- #
- if 0:
- blah
- blah
- #
- # Ho hum
- """
- output = []
- for piece in DocTestParser().parse(s):
- if isinstance(piece, Example):
- # Add the example's source code (strip trailing NL)
- output.append(piece.source[:-1])
- # Add the expected output:
- want = piece.want
- if want:
- output.append('# Expected:')
- output += ['## '+l for l in want.split('\n')[:-1]]
- else:
- # Add non-example text.
- output += [_comment_line(l)
- for l in piece.split('\n')[:-1]]
-
- # Trim junk on both ends.
- while output and output[-1] == '#':
- output.pop()
- while output and output[0] == '#':
- output.pop(0)
- # Combine the output, and return it.
- return '\n'.join(output)
-
-def testsource(module, name):
- """Extract the test sources from a doctest docstring as a script.
-
- Provide the module (or dotted name of the module) containing the
- test to be debugged and the name (within the module) of the object
- with the doc string with tests to be debugged.
- """
- module = _normalize_module(module)
- tests = DocTestFinder().find(module)
- test = [t for t in tests if t.name == name]
- if not test:
- raise ValueError(name, "not found in tests")
- test = test[0]
- testsrc = script_from_examples(test.docstring)
- return testsrc
-
-def debug_src(src, pm=False, globs=None):
- """Debug a single doctest docstring, in argument `src`'"""
- testsrc = script_from_examples(src)
- debug_script(testsrc, pm, globs)
-
-def debug_script(src, pm=False, globs=None):
- "Debug a test script. `src` is the script, as a string."
- import pdb
-
- # Note that tempfile.NameTemporaryFile() cannot be used. As the
- # docs say, a file so created cannot be opened by name a second time
- # on modern Windows boxes, and execfile() needs to open it.
- srcfilename = tempfile.mktemp(".py", "doctestdebug")
- f = open(srcfilename, 'w')
- f.write(src)
- f.close()
-
- try:
- if globs:
- globs = globs.copy()
- else:
- globs = {}
-
- if pm:
- try:
- execfile(srcfilename, globs, globs)
- except:
- print sys.exc_info()[1]
- pdb.post_mortem(sys.exc_info()[2])
- else:
- # Note that %r is vital here. '%s' instead can, e.g., cause
- # backslashes to get treated as metacharacters on Windows.
- pdb.run("execfile(%r)" % srcfilename, globs, globs)
-
- finally:
- os.remove(srcfilename)
-
-def debug(module, name, pm=False):
- """Debug a single doctest docstring.
-
- Provide the module (or dotted name of the module) containing the
- test to be debugged and the name (within the module) of the object
- with the docstring with tests to be debugged.
- """
- module = _normalize_module(module)
- testsrc = testsource(module, name)
- debug_script(testsrc, pm, module.__dict__)
-
-######################################################################
-## 10. Example Usage
-######################################################################
-class _TestClass:
- """
- A pointless class, for sanity-checking of docstring testing.
-
- Methods:
- square()
- get()
-
- >>> _TestClass(13).get() + _TestClass(-12).get()
- 1
- >>> hex(_TestClass(13).square().get())
- '0xa9'
- """
-
- def __init__(self, val):
- """val -> _TestClass object with associated value val.
-
- >>> t = _TestClass(123)
- >>> print t.get()
- 123
- """
-
- self.val = val
-
- def square(self):
- """square() -> square TestClass's associated value
-
- >>> _TestClass(13).square().get()
- 169
- """
-
- self.val = self.val ** 2
- return self
-
- def get(self):
- """get() -> return TestClass's associated value.
-
- >>> x = _TestClass(-42)
- >>> print x.get()
- -42
- """
-
- return self.val
-
-__test__ = {"_TestClass": _TestClass,
- "string": r"""
- Example of a string object, searched as-is.
- >>> x = 1; y = 2
- >>> x + y, x * y
- (3, 2)
- """,
-
- "bool-int equivalence": r"""
- In 2.2, boolean expressions displayed
- 0 or 1. By default, we still accept
- them. This can be disabled by passing
- DONT_ACCEPT_TRUE_FOR_1 to the new
- optionflags argument.
- >>> 4 == 4
- 1
- >>> 4 == 4
- True
- >>> 4 > 4
- 0
- >>> 4 > 4
- False
- """,
-
- "blank lines": r"""
- Blank lines can be marked with <BLANKLINE>:
- >>> print 'foo\n\nbar\n'
- foo
- <BLANKLINE>
- bar
- <BLANKLINE>
- """,
-
- "ellipsis": r"""
- If the ellipsis flag is used, then '...' can be used to
- elide substrings in the desired output:
- >>> print range(1000) #doctest: +ELLIPSIS
- [0, 1, 2, ..., 999]
- """,
-
- "whitespace normalization": r"""
- If the whitespace normalization flag is used, then
- differences in whitespace are ignored.
- >>> print range(30) #doctest: +NORMALIZE_WHITESPACE
- [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
- 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
- 27, 28, 29]
- """,
- }
-
-def _test():
- r = unittest.TextTestRunner()
- r.run(DocTestSuite())
-
-if __name__ == "__main__":
- _test()
diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py
index 78d943eb97..5638865f31 100644
--- a/tests/modeltests/basic/models.py
+++ b/tests/modeltests/basic/models.py
@@ -13,8 +13,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
-API_TESTS = """
-
+__test__ = {'API_TESTS': """
# No articles are in the system yet.
>>> Article.objects.all()
[]
@@ -87,6 +86,10 @@ DoesNotExist: Article matching query does not exist.
>>> Article.objects.get(pk=1)
<Article: Area woman programs in Python>
+# pk can be used as a shortcut for the primary key name in any query
+>>> Article.objects.filter(pk__in=[1])
+[<Article: Area woman programs in Python>]
+
# Model instances of the same type and same ID are considered equal.
>>> a = Article.objects.get(pk=1)
>>> b = Article.objects.get(pk=1)
@@ -314,14 +317,14 @@ AttributeError: Manager isn't accessible via Article instances
>>> Article.objects.all()
[<Article: Article 6>, <Article: Default headline>, <Article: Article 7>, <Article: Updated article 8>]
-"""
+"""}
from django.conf import settings
building_docs = getattr(settings, 'BUILDING_DOCS', False)
if building_docs or settings.DATABASE_ENGINE == 'postgresql':
- API_TESTS += """
+ __test__['API_TESTS'] += """
# In PostgreSQL, microsecond-level precision is available.
>>> a9 = Article(headline='Article 9', pub_date=datetime(2005, 7, 31, 12, 30, 45, 180))
>>> a9.save()
@@ -330,7 +333,7 @@ datetime.datetime(2005, 7, 31, 12, 30, 45, 180)
"""
if building_docs or settings.DATABASE_ENGINE == 'mysql':
- API_TESTS += """
+ __test__['API_TESTS'] += """
# In MySQL, microsecond-level precision isn't available. You'll lose
# microsecond-level precision once the data is saved.
>>> a9 = Article(headline='Article 9', pub_date=datetime(2005, 7, 31, 12, 30, 45, 180))
@@ -339,7 +342,7 @@ if building_docs or settings.DATABASE_ENGINE == 'mysql':
datetime.datetime(2005, 7, 31, 12, 30, 45)
"""
-API_TESTS += """
+__test__['API_TESTS'] += """
# You can manually specify the primary key when creating a new object.
>>> a101 = Article(id=101, headline='Article 101', pub_date=datetime(2005, 7, 31, 12, 30, 45))
diff --git a/tests/modeltests/choices/models.py b/tests/modeltests/choices/models.py
index 881fb29fd2..37d36fe1d8 100644
--- a/tests/modeltests/choices/models.py
+++ b/tests/modeltests/choices/models.py
@@ -23,7 +23,7 @@ class Person(models.Model):
def __str__(self):
return self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> a = Person(name='Adrian', gender='M')
>>> a.save()
>>> s = Person(name='Sara', gender='F')
@@ -36,4 +36,4 @@ API_TESTS = """
'Male'
>>> s.get_gender_display()
'Female'
-"""
+"""}
diff --git a/tests/modeltests/custom_columns/models.py b/tests/modeltests/custom_columns/models.py
index 7d8c52d137..e88fa80da2 100644
--- a/tests/modeltests/custom_columns/models.py
+++ b/tests/modeltests/custom_columns/models.py
@@ -15,7 +15,7 @@ class Person(models.Model):
def __str__(self):
return '%s %s' % (self.first_name, self.last_name)
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create a Person.
>>> p = Person(first_name='John', last_name='Smith')
>>> p.save()
@@ -50,4 +50,4 @@ AttributeError: 'Person' object has no attribute 'firstname'
Traceback (most recent call last):
...
AttributeError: 'Person' object has no attribute 'last'
-"""
+"""}
diff --git a/tests/modeltests/custom_managers/models.py b/tests/modeltests/custom_managers/models.py
index 1c4e91b526..99df875275 100644
--- a/tests/modeltests/custom_managers/models.py
+++ b/tests/modeltests/custom_managers/models.py
@@ -58,7 +58,7 @@ class Car(models.Model):
def __str__(self):
return self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> p1 = Person(first_name='Bugs', last_name='Bunny', fun=True)
>>> p1.save()
>>> p2 = Person(first_name='Droopy', last_name='Dog', fun=False)
@@ -104,4 +104,4 @@ True
# to the first manager defined in the class. In this case, it's "cars".
>>> Car._default_manager.order_by('name')
[<Car: Corvette>, <Car: Neon>]
-"""
+"""}
diff --git a/tests/modeltests/custom_methods/models.py b/tests/modeltests/custom_methods/models.py
index e314d97264..e8fb751d54 100644
--- a/tests/modeltests/custom_methods/models.py
+++ b/tests/modeltests/custom_methods/models.py
@@ -36,7 +36,7 @@ class Article(models.Model):
# positional arguments to Article().
return [self.__class__(*row) for row in cursor.fetchall()]
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create a couple of Articles.
>>> from datetime import date
>>> a = Article(id=None, headline='Area man programs in Python', pub_date=date(2005, 7, 27))
@@ -55,4 +55,4 @@ False
[<Article: Area man programs in Python>]
>>> b.articles_from_same_day_2()
[<Article: Area man programs in Python>]
-"""
+"""}
diff --git a/tests/modeltests/custom_pk/models.py b/tests/modeltests/custom_pk/models.py
index f7b790ca21..fd0901da3c 100644
--- a/tests/modeltests/custom_pk/models.py
+++ b/tests/modeltests/custom_pk/models.py
@@ -27,7 +27,7 @@ class Business(models.Model):
def __str__(self):
return self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> dan = Employee(employee_code='ABC123', first_name='Dan', last_name='Jones')
>>> dan.save()
>>> Employee.objects.all()
@@ -51,6 +51,10 @@ DoesNotExist: Employee matching query does not exist.
>>> Employee.objects.get(employee_code__exact='ABC123')
<Employee: Dan Jones>
+# pk can be used as a substitute for the primary key.
+>>> Employee.objects.filter(pk__in=['ABC123','XYZ456'])
+[<Employee: Fran Bones>, <Employee: Dan Jones>]
+
# Fran got married and changed her last name.
>>> fran = Employee.objects.get(pk='XYZ456')
>>> fran.last_name = 'Jones'
@@ -88,4 +92,4 @@ DoesNotExist: Employee matching query does not exist.
>>> Business.objects.filter(employees__first_name__startswith='Fran')
[<Business: Sears>]
-"""
+"""}
diff --git a/tests/modeltests/empty/models.py b/tests/modeltests/empty/models.py
index c50878398d..0e5d572504 100644
--- a/tests/modeltests/empty/models.py
+++ b/tests/modeltests/empty/models.py
@@ -10,7 +10,7 @@ from django.db import models
class Empty(models.Model):
pass
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> m = Empty()
>>> m.id
>>> m.save()
@@ -20,5 +20,7 @@ API_TESTS = """
2
>>> m.id is not None
True
+>>> existing = Empty(m.id)
+>>> existing.save()
-"""
+"""}
diff --git a/tests/modeltests/field_defaults/models.py b/tests/modeltests/field_defaults/models.py
index 0d69ffd8be..da4cd38974 100644
--- a/tests/modeltests/field_defaults/models.py
+++ b/tests/modeltests/field_defaults/models.py
@@ -19,7 +19,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> from datetime import datetime
# No articles are in the system yet.
@@ -48,4 +48,4 @@ API_TESTS = """
>>> d = now - a.pub_date
>>> d.seconds < 5
True
-"""
+"""}
diff --git a/tests/modeltests/generic_relations/models.py b/tests/modeltests/generic_relations/models.py
index e9a81a19e8..eb64d7ec3d 100644
--- a/tests/modeltests/generic_relations/models.py
+++ b/tests/modeltests/generic_relations/models.py
@@ -53,7 +53,7 @@ class Mineral(models.Model):
def __str__(self):
return self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create the world in 7 lines of code...
>>> lion = Animal(common_name="Lion", latin_name="Panthera leo")
>>> platypus = Animal(common_name="Platypus", latin_name="Ornithorhynchus anatinus")
@@ -105,4 +105,4 @@ API_TESTS = """
[<TaggedItem: shiny>]
>>> TaggedItem.objects.filter(content_type__pk=ctype.id, object_id=quartz.id)
[<TaggedItem: clearish>]
-"""
+"""}
diff --git a/tests/modeltests/get_latest/models.py b/tests/modeltests/get_latest/models.py
index 42e7a14ec7..84c6273818 100644
--- a/tests/modeltests/get_latest/models.py
+++ b/tests/modeltests/get_latest/models.py
@@ -3,9 +3,9 @@
Models can have a ``get_latest_by`` attribute, which should be set to the name
of a DateField or DateTimeField. If ``get_latest_by`` exists, the model's
-module will get a ``get_latest()`` function, which will return the latest
-object in the database according to that field. "Latest" means "having the
-date farthest into the future."
+manager will get a ``latest()`` method, which will return the latest object in
+the database according to that field. "Latest" means "having the date farthest
+into the future."
"""
from django.db import models
@@ -29,8 +29,8 @@ class Person(models.Model):
def __str__(self):
return self.name
-API_TESTS = """
-# Because no Articles exist yet, get_latest() raises ArticleDoesNotExist.
+__test__ = {'API_TESTS':"""
+# Because no Articles exist yet, latest() raises ArticleDoesNotExist.
>>> Article.objects.latest()
Traceback (most recent call last):
...
@@ -76,4 +76,4 @@ AssertionError: latest() requires either a field_name parameter or 'get_latest_b
>>> Person.objects.latest('birthday')
<Person: Stephanie>
-"""
+"""}
diff --git a/tests/modeltests/get_or_create/models.py b/tests/modeltests/get_or_create/models.py
index 10a8721afc..b4f39ceded 100644
--- a/tests/modeltests/get_or_create/models.py
+++ b/tests/modeltests/get_or_create/models.py
@@ -15,7 +15,7 @@ class Person(models.Model):
def __str__(self):
return '%s %s' % (self.first_name, self.last_name)
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Acting as a divine being, create an Person.
>>> from datetime import date
>>> p = Person(first_name='John', last_name='Lennon', birthday=date(1940, 10, 9))
@@ -49,4 +49,4 @@ True
False
>>> Person.objects.count()
2
-"""
+"""}
diff --git a/tests/modeltests/invalid_models/models.py b/tests/modeltests/invalid_models/models.py
index eb305b4e92..2299cd85e6 100644
--- a/tests/modeltests/invalid_models/models.py
+++ b/tests/modeltests/invalid_models/models.py
@@ -68,17 +68,36 @@ class SelfClashForeign(models.Model):
foreign_1 = models.ForeignKey("SelfClashForeign", related_name='id')
foreign_2 = models.ForeignKey("SelfClashForeign", related_name='src_safe')
+class ValidM2M(models.Model):
+ src_safe = models.CharField(maxlength=10)
+ validm2m = models.CharField(maxlength=10)
+
+ # M2M fields are symmetrical by default. Symmetrical M2M fields
+ # on self don't require a related accessor, so many potential
+ # clashes are avoided.
+ validm2m_set = models.ManyToManyField("ValidM2M")
+
+ m2m_1 = models.ManyToManyField("ValidM2M", related_name='id')
+ m2m_2 = models.ManyToManyField("ValidM2M", related_name='src_safe')
+
+ m2m_3 = models.ManyToManyField('self')
+ m2m_4 = models.ManyToManyField('self')
+
class SelfClashM2M(models.Model):
src_safe = models.CharField(maxlength=10)
selfclashm2m = models.CharField(maxlength=10)
- selfclashm2m_set = models.ManyToManyField("SelfClashM2M")
- m2m_1 = models.ManyToManyField("SelfClashM2M", related_name='id')
- m2m_2 = models.ManyToManyField("SelfClashM2M", related_name='src_safe')
-
+ # Non-symmetrical M2M fields _do_ have related accessors, so
+ # there is potential for clashes.
+ selfclashm2m_set = models.ManyToManyField("SelfClashM2M", symmetrical=False)
+
+ m2m_1 = models.ManyToManyField("SelfClashM2M", related_name='id', symmetrical=False)
+ m2m_2 = models.ManyToManyField("SelfClashM2M", related_name='src_safe', symmetrical=False)
+ m2m_3 = models.ManyToManyField('self', symmetrical=False)
+ m2m_4 = models.ManyToManyField('self', symmetrical=False)
-error_log = """invalid_models.fielderrors: "charfield": CharFields require a "maxlength" attribute.
+model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "maxlength" attribute.
invalid_models.fielderrors: "floatfield": FloatFields require a "decimal_places" attribute.
invalid_models.fielderrors: "floatfield": FloatFields require a "max_digits" attribute.
invalid_models.fielderrors: "filefield": FileFields require an "upload_to" attribute.
@@ -147,9 +166,17 @@ invalid_models.selfclashforeign: Accessor for field 'foreign_2' clashes with fie
invalid_models.selfclashforeign: Reverse query name for field 'foreign_2' clashes with field 'SelfClashForeign.src_safe'. Add a related_name argument to the definition for 'foreign_2'.
invalid_models.selfclashm2m: Accessor for m2m field 'selfclashm2m_set' clashes with m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'selfclashm2m_set'.
invalid_models.selfclashm2m: Reverse query name for m2m field 'selfclashm2m_set' clashes with field 'SelfClashM2M.selfclashm2m'. Add a related_name argument to the definition for 'selfclashm2m_set'.
+invalid_models.selfclashm2m: Accessor for m2m field 'selfclashm2m_set' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'selfclashm2m_set'.
invalid_models.selfclashm2m: Accessor for m2m field 'm2m_1' clashes with field 'SelfClashM2M.id'. Add a related_name argument to the definition for 'm2m_1'.
invalid_models.selfclashm2m: Accessor for m2m field 'm2m_2' clashes with field 'SelfClashM2M.src_safe'. Add a related_name argument to the definition for 'm2m_2'.
invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_1' clashes with field 'SelfClashM2M.id'. Add a related_name argument to the definition for 'm2m_1'.
invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_2' clashes with field 'SelfClashM2M.src_safe'. Add a related_name argument to the definition for 'm2m_2'.
+invalid_models.selfclashm2m: Accessor for m2m field 'm2m_3' clashes with m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_3'.
+invalid_models.selfclashm2m: Accessor for m2m field 'm2m_3' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_3'.
+invalid_models.selfclashm2m: Accessor for m2m field 'm2m_3' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_3'.
+invalid_models.selfclashm2m: Accessor for m2m field 'm2m_4' clashes with m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_4'.
+invalid_models.selfclashm2m: Accessor for m2m field 'm2m_4' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_4'.
+invalid_models.selfclashm2m: Accessor for m2m field 'm2m_4' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_4'.
+invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_3' clashes with field 'SelfClashM2M.selfclashm2m'. Add a related_name argument to the definition for 'm2m_3'.
+invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_4' clashes with field 'SelfClashM2M.selfclashm2m'. Add a related_name argument to the definition for 'm2m_4'.
"""
-
diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py
index a2c0a14158..09c3aa7aa8 100644
--- a/tests/modeltests/lookup/models.py
+++ b/tests/modeltests/lookup/models.py
@@ -15,7 +15,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
-API_TESTS = """
+__test__ = {'API_TESTS':r"""
# Create a couple of Articles.
>>> from datetime import datetime
>>> a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26))
@@ -161,13 +161,14 @@ DoesNotExist: Article matching query does not exist.
<Article: Article 1>
# Underscores and percent signs have special meaning in the underlying
-# database library, but Django handles the quoting of them automatically.
+# SQL code, but Django handles the quoting of them automatically.
>>> a8 = Article(headline='Article_ with underscore', pub_date=datetime(2005, 11, 20))
>>> a8.save()
>>> Article.objects.filter(headline__startswith='Article')
[<Article: Article_ with underscore>, <Article: Article 5>, <Article: Article 6>, <Article: Article 4>, <Article: Article 2>, <Article: Article 3>, <Article: Article 7>, <Article: Article 1>]
>>> Article.objects.filter(headline__startswith='Article_')
[<Article: Article_ with underscore>]
+
>>> a9 = Article(headline='Article% with percent sign', pub_date=datetime(2005, 11, 21))
>>> a9.save()
>>> Article.objects.filter(headline__startswith='Article')
@@ -182,4 +183,12 @@ DoesNotExist: Article matching query does not exist.
[<Article: Article% with percent sign>, <Article: Article 5>, <Article: Article 6>, <Article: Article 4>, <Article: Article 2>, <Article: Article 3>, <Article: Article 7>, <Article: Article 1>]
>>> Article.objects.exclude(headline="Article 7")
[<Article: Article% with percent sign>, <Article: Article_ with underscore>, <Article: Article 5>, <Article: Article 6>, <Article: Article 4>, <Article: Article 2>, <Article: Article 3>, <Article: Article 1>]
-"""
+
+# Backslashes also have special meaning in the underlying SQL code, but Django
+# automatically quotes them appropriately.
+>>> a10 = Article(headline='Article with \\ backslash', pub_date=datetime(2005, 11, 22))
+>>> a10.save()
+>>> Article.objects.filter(headline__contains='\\')
+[<Article: Article with \ backslash>]
+
+"""}
diff --git a/tests/modeltests/m2m_and_m2o/models.py b/tests/modeltests/m2m_and_m2o/models.py
index f43fb12d9e..7fc66ed5a0 100644
--- a/tests/modeltests/m2m_and_m2o/models.py
+++ b/tests/modeltests/m2m_and_m2o/models.py
@@ -21,7 +21,7 @@ class Issue(models.Model):
ordering = ('num',)
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> Issue.objects.all()
[]
>>> r = User(username='russell')
@@ -62,4 +62,4 @@ API_TESTS = """
[<Issue: 1>, <Issue: 2>, <Issue: 3>]
>>> Issue.objects.filter(Q(client=r.id) | Q(cc__id__exact=r.id))
[<Issue: 1>, <Issue: 2>, <Issue: 3>]
-"""
+"""}
diff --git a/tests/modeltests/m2m_intermediary/models.py b/tests/modeltests/m2m_intermediary/models.py
index 848d035c39..b917db6189 100644
--- a/tests/modeltests/m2m_intermediary/models.py
+++ b/tests/modeltests/m2m_intermediary/models.py
@@ -34,7 +34,7 @@ class Writer(models.Model):
def __str__(self):
return '%s (%s)' % (self.reporter, self.position)
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create a few Reporters.
>>> r1 = Reporter(first_name='John', last_name='Smith')
>>> r1.save()
@@ -65,4 +65,4 @@ API_TESTS = """
<Article: This is a test>
>>> r1.writer_set.all()
[<Writer: John Smith (Main writer)>]
-"""
+"""}
diff --git a/tests/modeltests/m2m_multiple/models.py b/tests/modeltests/m2m_multiple/models.py
index e4fef75f19..5a1aa122a9 100644
--- a/tests/modeltests/m2m_multiple/models.py
+++ b/tests/modeltests/m2m_multiple/models.py
@@ -28,7 +28,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> from datetime import datetime
>>> c1 = Category(name='Sports')
@@ -76,4 +76,4 @@ API_TESTS = """
[]
>>> c4.secondary_article_set.all()
[<Article: Area man steals>, <Article: Area man runs>]
-"""
+"""}
diff --git a/tests/modeltests/m2m_recursive/models.py b/tests/modeltests/m2m_recursive/models.py
index dace32d565..9f31cf92c0 100644
--- a/tests/modeltests/m2m_recursive/models.py
+++ b/tests/modeltests/m2m_recursive/models.py
@@ -22,7 +22,7 @@ class Person(models.Model):
def __str__(self):
return self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> a = Person(name='Anne')
>>> a.save()
>>> b = Person(name='Bill')
@@ -189,4 +189,4 @@ API_TESTS = """
>>> d.stalkers.all()
[<Person: Chuck>]
-"""
+"""}
diff --git a/tests/modeltests/m2o_recursive/models.py b/tests/modeltests/m2o_recursive/models.py
index 44881b5a2f..0b528faf9e 100644
--- a/tests/modeltests/m2o_recursive/models.py
+++ b/tests/modeltests/m2o_recursive/models.py
@@ -19,7 +19,7 @@ class Category(models.Model):
def __str__(self):
return self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create a few Category objects.
>>> r = Category(id=None, name='Root category', parent=None)
>>> r.save()
@@ -37,4 +37,4 @@ None
[]
>>> c.parent
<Category: Root category>
-"""
+"""}
diff --git a/tests/modeltests/m2o_recursive2/models.py b/tests/modeltests/m2o_recursive2/models.py
index 93185c6a4c..5b7c5447ad 100644
--- a/tests/modeltests/m2o_recursive2/models.py
+++ b/tests/modeltests/m2o_recursive2/models.py
@@ -17,7 +17,7 @@ class Person(models.Model):
def __str__(self):
return self.full_name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create two Person objects -- the mom and dad in our family.
>>> dad = Person(full_name='John Smith Senior', mother=None, father=None)
>>> dad.save()
@@ -40,4 +40,4 @@ API_TESTS = """
[]
>>> kid.fathers_child_set.all()
[]
-"""
+"""}
diff --git a/tests/modeltests/manipulators/models.py b/tests/modeltests/manipulators/models.py
index f7b20d52ac..e5b8be55b5 100644
--- a/tests/modeltests/manipulators/models.py
+++ b/tests/modeltests/manipulators/models.py
@@ -21,7 +21,7 @@ class Album(models.Model):
def __str__(self):
return self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> from django.utils.datastructures import MultiValueDict
# Create a Musician object via the default AddManipulator.
@@ -88,4 +88,4 @@ True
<Album: Ultimate Ella>
>>> a2.release_date
datetime.date(2005, 2, 13)
-"""
+"""}
diff --git a/tests/modeltests/many_to_many/models.py b/tests/modeltests/many_to_many/models.py
index 0e989a0fbe..357f3ca629 100644
--- a/tests/modeltests/many_to_many/models.py
+++ b/tests/modeltests/many_to_many/models.py
@@ -28,7 +28,7 @@ class Article(models.Model):
class Meta:
ordering = ('headline',)
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create a couple of Publications.
>>> p1 = Publication(id=None, title='The Python Journal')
>>> p1.save()
@@ -231,4 +231,4 @@ API_TESTS = """
>>> p1.article_set.all()
[<Article: NASA uses Python>]
-"""
+"""}
diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py
index d202975128..82eb3257d0 100644
--- a/tests/modeltests/many_to_one/models.py
+++ b/tests/modeltests/many_to_one/models.py
@@ -25,7 +25,7 @@ class Article(models.Model):
class Meta:
ordering = ('headline',)
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create a few Reporters.
>>> r = Reporter(first_name='John', last_name='Smith', email='john@example.com')
>>> r.save()
@@ -263,4 +263,4 @@ TypeError: Cannot resolve keyword 'reporter_id' into field
>>> Article.objects.all()
[]
-"""
+"""}
diff --git a/tests/modeltests/many_to_one_null/models.py b/tests/modeltests/many_to_one_null/models.py
index b1936b9861..fb0f6ac3b7 100644
--- a/tests/modeltests/many_to_one_null/models.py
+++ b/tests/modeltests/many_to_one_null/models.py
@@ -23,7 +23,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create a Reporter.
>>> r = Reporter(name='John Smith')
>>> r.save()
@@ -121,4 +121,4 @@ DoesNotExist: <Article: Fourth> is not related to <Reporter: John Smith>.
>>> Article.objects.filter(reporter__isnull=True)
[<Article: First>, <Article: Fourth>]
-"""
+"""}
diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py
index 473cf24a9f..babef73e0a 100644
--- a/tests/modeltests/model_inheritance/models.py
+++ b/tests/modeltests/model_inheritance/models.py
@@ -26,7 +26,7 @@ class ItalianRestaurant(Restaurant):
def __str__(self):
return "%s the italian restaurant" % self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Make sure Restaurant has the right fields in the right order.
>>> [f.name for f in Restaurant._meta.fields]
['id', 'name', 'address', 'serves_hot_dogs', 'serves_pizza']
@@ -50,4 +50,4 @@ API_TESTS = """
>>> ir.save()
-"""
+"""}
diff --git a/tests/modeltests/mutually_referential/models.py b/tests/modeltests/mutually_referential/models.py
index 07b52effbc..5a3897d21a 100644
--- a/tests/modeltests/mutually_referential/models.py
+++ b/tests/modeltests/mutually_referential/models.py
@@ -14,7 +14,7 @@ class Child(Model):
name = CharField(maxlength=100)
parent = ForeignKey(Parent)
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create a Parent
>>> q = Parent(name='Elizabeth')
>>> q.save()
@@ -29,4 +29,4 @@ API_TESTS = """
>>> q.delete()
-""" \ No newline at end of file
+"""} \ No newline at end of file
diff --git a/tests/modeltests/one_to_one/models.py b/tests/modeltests/one_to_one/models.py
index f95556c08d..7488204ff1 100644
--- a/tests/modeltests/one_to_one/models.py
+++ b/tests/modeltests/one_to_one/models.py
@@ -30,7 +30,15 @@ class Waiter(models.Model):
def __str__(self):
return "%s the waiter at %s" % (self.name, self.restaurant)
-API_TESTS = """
+class ManualPrimaryKey(models.Model):
+ primary_key = models.CharField(maxlength=10, primary_key=True)
+ name = models.CharField(maxlength = 50)
+
+class RelatedModel(models.Model):
+ link = models.OneToOneField(ManualPrimaryKey)
+ name = models.CharField(maxlength = 50)
+
+__test__ = {'API_TESTS':"""
# Create a couple of Places.
>>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton')
>>> p1.save()
@@ -151,4 +159,10 @@ DoesNotExist: Restaurant matching query does not exist.
# Delete the restaurant; the waiter should also be removed
>>> r = Restaurant.objects.get(pk=1)
>>> r.delete()
-"""
+
+# One-to-one fields still work if you create your own primary key
+>>> o1 = ManualPrimaryKey(primary_key="abc123", name="primary")
+>>> o1.save()
+>>> o2 = RelatedModel(link=o1, name="secondary")
+>>> o2.save()
+"""}
diff --git a/tests/modeltests/or_lookups/models.py b/tests/modeltests/or_lookups/models.py
index 80dc35f2b1..2de18edc1f 100644
--- a/tests/modeltests/or_lookups/models.py
+++ b/tests/modeltests/or_lookups/models.py
@@ -23,7 +23,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> from datetime import datetime
>>> from django.db.models import Q
@@ -101,4 +101,4 @@ API_TESTS = """
[<Article: Hello>]
>>> Article.objects.complex_filter(Q(pk=1) | Q(pk=2))
[<Article: Hello>, <Article: Goodbye>]
-"""
+"""}
diff --git a/tests/modeltests/ordering/models.py b/tests/modeltests/ordering/models.py
index b22568c900..110ae3d7fc 100644
--- a/tests/modeltests/ordering/models.py
+++ b/tests/modeltests/ordering/models.py
@@ -24,7 +24,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create a couple of Articles.
>>> from datetime import datetime
>>> a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26))
@@ -64,4 +64,4 @@ API_TESTS = """
# don't know what order the output will be in.
>>> Article.objects.order_by('?')
[...]
-"""
+"""}
diff --git a/tests/modeltests/pagination/models.py b/tests/modeltests/pagination/models.py
index 165b251d35..ea2385dc79 100644
--- a/tests/modeltests/pagination/models.py
+++ b/tests/modeltests/pagination/models.py
@@ -15,7 +15,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# prepare a list of objects for pagination
>>> from datetime import datetime
>>> for x in range(1, 10):
@@ -64,4 +64,4 @@ True
>>> paginator.last_on_page(1)
9
-"""
+"""}
diff --git a/tests/modeltests/properties/models.py b/tests/modeltests/properties/models.py
index 3b0133bf8a..4ba6b1a808 100644
--- a/tests/modeltests/properties/models.py
+++ b/tests/modeltests/properties/models.py
@@ -20,7 +20,7 @@ class Person(models.Model):
full_name_2 = property(_get_full_name, _set_full_name)
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> a = Person(first_name='John', last_name='Lennon')
>>> a.save()
>>> a.full_name
@@ -37,4 +37,4 @@ AttributeError: can't set attribute
>>> a2.save()
>>> a2.first_name
'Paul'
-"""
+"""}
diff --git a/tests/modeltests/reserved_names/models.py b/tests/modeltests/reserved_names/models.py
index db9196bebe..affe3f649d 100644
--- a/tests/modeltests/reserved_names/models.py
+++ b/tests/modeltests/reserved_names/models.py
@@ -24,7 +24,7 @@ class Thing(models.Model):
def __str__(self):
return self.when
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> import datetime
>>> day1 = datetime.date(2005, 1, 1)
>>> day2 = datetime.date(2006, 2, 2)
@@ -53,4 +53,4 @@ b
>>> Thing.objects.filter(where__month=1)
[<Thing: a>]
-"""
+"""}
diff --git a/tests/modeltests/reverse_lookup/models.py b/tests/modeltests/reverse_lookup/models.py
index b8c4466021..7e6712676f 100644
--- a/tests/modeltests/reverse_lookup/models.py
+++ b/tests/modeltests/reverse_lookup/models.py
@@ -27,7 +27,7 @@ class Choice(models.Model):
def __str(self):
return self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> john = User(name="John Doe")
>>> john.save()
>>> jim = User(name="Jim Bo")
@@ -56,4 +56,4 @@ API_TESTS = """
Traceback (most recent call last):
...
TypeError: Cannot resolve keyword 'choice' into field
-"""
+"""}
diff --git a/tests/modeltests/save_delete_hooks/models.py b/tests/modeltests/save_delete_hooks/models.py
index f01a2932d3..6e24c308ba 100644
--- a/tests/modeltests/save_delete_hooks/models.py
+++ b/tests/modeltests/save_delete_hooks/models.py
@@ -24,7 +24,7 @@ class Person(models.Model):
super(Person, self).delete() # Call the "real" delete() method
print "After deletion"
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> p1 = Person(first_name='John', last_name='Smith')
>>> p1.save()
Before save
@@ -39,4 +39,4 @@ After deletion
>>> Person.objects.all()
[]
-"""
+"""}
diff --git a/tests/modeltests/serializers/models.py b/tests/modeltests/serializers/models.py
index ccf565c365..d1d10b43c0 100644
--- a/tests/modeltests/serializers/models.py
+++ b/tests/modeltests/serializers/models.py
@@ -37,7 +37,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create some data:
>>> from datetime import datetime
>>> sports = Category(name="Sports")
@@ -118,4 +118,4 @@ API_TESTS = """
>>> Article.objects.all()
[<Article: Just kidding; I love TV poker>, <Article: Time to reform copyright>]
-"""
+"""}
diff --git a/tests/modeltests/str/models.py b/tests/modeltests/str/models.py
index 4e4228ac89..81230d538c 100644
--- a/tests/modeltests/str/models.py
+++ b/tests/modeltests/str/models.py
@@ -17,7 +17,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create an Article.
>>> from datetime import datetime
>>> a = Article(headline='Area man programs in Python', pub_date=datetime(2005, 7, 28))
@@ -28,4 +28,4 @@ API_TESTS = """
>>> a
<Article: Area man programs in Python>
-"""
+"""}
diff --git a/tests/othertests/__init__.py b/tests/modeltests/test_client/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/tests/othertests/__init__.py
+++ b/tests/modeltests/test_client/__init__.py
diff --git a/tests/modeltests/test_client/management.py b/tests/modeltests/test_client/management.py
new file mode 100644
index 0000000000..9b5a5c498e
--- /dev/null
+++ b/tests/modeltests/test_client/management.py
@@ -0,0 +1,10 @@
+from django.dispatch import dispatcher
+from django.db.models import signals
+import models as test_client_app
+from django.contrib.auth.models import User
+
+def setup_test(app, created_models, verbosity):
+ # Create a user account for the login-based tests
+ User.objects.create_user('testclient','testclient@example.com', 'password')
+
+dispatcher.connect(setup_test, sender=test_client_app, signal=signals.post_syncdb)
diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py
new file mode 100644
index 0000000000..c5b1a241ca
--- /dev/null
+++ b/tests/modeltests/test_client/models.py
@@ -0,0 +1,101 @@
+"""
+39. Testing using the Test Client
+
+The test client is a class that can act like a simple
+browser for testing purposes.
+
+It allows the user to compose GET and POST requests, and
+obtain the response that the server gave to those requests.
+The server Response objects are annotated with the details
+of the contexts and templates that were rendered during the
+process of serving the request.
+
+Client objects are stateful - they will retain cookie (and
+thus session) details for the lifetime of the Client instance.
+
+This is not intended as a replacement for Twill,Selenium, or
+other browser automation frameworks - it is here to allow
+testing against the contexts and templates produced by a view,
+rather than the HTML rendered to the end-user.
+
+"""
+from django.test.client import Client
+import unittest
+
+class ClientTest(unittest.TestCase):
+ def setUp(self):
+ "Set up test environment"
+ self.client = Client()
+
+ def test_get_view(self):
+ "GET a view"
+ response = self.client.get('/test_client/get_view/')
+
+ # Check some response details
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['var'], 42)
+ self.assertEqual(response.template.name, 'GET Template')
+ self.failUnless('This is a test.' in response.content)
+
+ def test_get_post_view(self):
+ "GET a view that normally expects POSTs"
+ response = self.client.get('/test_client/post_view/', {})
+
+ # Check some response details
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.template.name, 'Empty POST Template')
+
+ def test_empty_post(self):
+ "POST an empty dictionary to a view"
+ response = self.client.post('/test_client/post_view/', {})
+
+ # Check some response details
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.template.name, 'Empty POST Template')
+
+ def test_post_view(self):
+ "POST some data to a view"
+ post_data = {
+ 'value': 37
+ }
+ response = self.client.post('/test_client/post_view/', post_data)
+
+ # Check some response details
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['data'], '37')
+ self.assertEqual(response.template.name, 'POST Template')
+ self.failUnless('Data received' in response.content)
+
+ def test_redirect(self):
+ "GET a URL that redirects elsewhere"
+ response = self.client.get('/test_client/redirect_view/')
+
+ # Check that the response was a 302 (redirect)
+ self.assertEqual(response.status_code, 302)
+
+ def test_unknown_page(self):
+ "GET an invalid URL"
+ response = self.client.get('/test_client/unknown_view/')
+
+ # Check that the response was a 404
+ self.assertEqual(response.status_code, 404)
+
+ def test_view_with_login(self):
+ "Request a page that is protected with @login_required"
+
+ # Get the page without logging in. Should result in 302.
+ response = self.client.get('/test_client/login_protected_view/')
+ self.assertEqual(response.status_code, 302)
+
+ # Request a page that requires a login
+ response = self.client.login('/test_client/login_protected_view/', 'testclient', 'password')
+ self.assertTrue(response)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['user'].username, 'testclient')
+ self.assertEqual(response.template.name, 'Login Template')
+
+ def test_view_with_bad_login(self):
+ "Request a page that is protected with @login, but use bad credentials"
+
+ response = self.client.login('/test_client/login_protected_view/', 'otheruser', 'nopassword')
+ self.assertFalse(response)
diff --git a/tests/modeltests/test_client/urls.py b/tests/modeltests/test_client/urls.py
new file mode 100644
index 0000000000..09bba5c007
--- /dev/null
+++ b/tests/modeltests/test_client/urls.py
@@ -0,0 +1,9 @@
+from django.conf.urls.defaults import *
+import views
+
+urlpatterns = patterns('',
+ (r'^get_view/$', views.get_view),
+ (r'^post_view/$', views.post_view),
+ (r'^redirect_view/$', views.redirect_view),
+ (r'^login_protected_view/$', views.login_protected_view),
+)
diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py
new file mode 100644
index 0000000000..bf131032eb
--- /dev/null
+++ b/tests/modeltests/test_client/views.py
@@ -0,0 +1,35 @@
+from django.template import Context, Template
+from django.http import HttpResponse, HttpResponseRedirect
+from django.contrib.auth.decorators import login_required
+
+def get_view(request):
+ "A simple view that expects a GET request, and returns a rendered template"
+ t = Template('This is a test. {{ var }} is the value.', name='GET Template')
+ c = Context({'var': 42})
+
+ return HttpResponse(t.render(c))
+
+def post_view(request):
+ """A view that expects a POST, and returns a different template depending
+ on whether any POST data is available
+ """
+ if request.POST:
+ t = Template('Data received: {{ data }} is the value.', name='POST Template')
+ c = Context({'data': request.POST['value']})
+ else:
+ t = Template('Viewing POST page.', name='Empty POST Template')
+ c = Context()
+
+ return HttpResponse(t.render(c))
+
+def redirect_view(request):
+ "A view that redirects all requests to the GET view"
+ return HttpResponseRedirect('/test_client/get_view/')
+
+@login_required
+def login_protected_view(request):
+ "A simple view that is login protected."
+ t = Template('This is a login protected test. Username is {{ user.username }}.', name='Login Template')
+ c = Context({'user': request.user})
+
+ return HttpResponse(t.render(c))
diff --git a/tests/modeltests/transactions/models.py b/tests/modeltests/transactions/models.py
index 92e0f38f47..e1fad8063e 100644
--- a/tests/modeltests/transactions/models.py
+++ b/tests/modeltests/transactions/models.py
@@ -17,16 +17,16 @@ class Reporter(models.Model):
def __str__(self):
return "%s %s" % (self.first_name, self.last_name)
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> from django.db import connection, transaction
-"""
+"""}
from django.conf import settings
building_docs = getattr(settings, 'BUILDING_DOCS', False)
if building_docs or settings.DATABASE_ENGINE != 'mysql':
- API_TESTS += """
+ __test__['API_TESTS'] += """
# the default behavior is to autocommit after each save() action
>>> def create_a_reporter_then_fail(first, last):
... a = Reporter(first_name=first, last_name=last)
diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py
index 57811f25a5..a9a3d3f485 100644
--- a/tests/modeltests/validation/models.py
+++ b/tests/modeltests/validation/models.py
@@ -20,7 +20,7 @@ class Person(models.Model):
def __str__(self):
return self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> import datetime
>>> valid_params = {
@@ -146,4 +146,4 @@ u'john@example.com'
>>> p.validate()
{'email': ['Enter a valid e-mail address.']}
-"""
+"""}
diff --git a/tests/othertests/cache.py b/tests/othertests/cache.py
deleted file mode 100644
index 81f2c20328..0000000000
--- a/tests/othertests/cache.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# Unit tests for cache framework
-# Uses whatever cache backend is set in the test settings file.
-
-from django.core.cache import cache
-import time
-
-# functions/classes for complex data type tests
-def f():
- return 42
-class C:
- def m(n):
- return 24
-
-# simple set/get
-cache.set("key", "value")
-assert cache.get("key") == "value"
-
-# get with non-existent keys
-assert cache.get("does not exist") is None
-assert cache.get("does not exist", "bang!") == "bang!"
-
-# get_many
-cache.set('a', 'a')
-cache.set('b', 'b')
-cache.set('c', 'c')
-cache.set('d', 'd')
-assert cache.get_many(['a', 'c', 'd']) == {'a' : 'a', 'c' : 'c', 'd' : 'd'}
-assert cache.get_many(['a', 'b', 'e']) == {'a' : 'a', 'b' : 'b'}
-
-# delete
-cache.set("key1", "spam")
-cache.set("key2", "eggs")
-assert cache.get("key1") == "spam"
-cache.delete("key1")
-assert cache.get("key1") is None
-assert cache.get("key2") == "eggs"
-
-# has_key
-cache.set("hello", "goodbye")
-assert cache.has_key("hello") == True
-assert cache.has_key("goodbye") == False
-
-# test data types
-stuff = {
- 'string' : 'this is a string',
- 'int' : 42,
- 'list' : [1, 2, 3, 4],
- 'tuple' : (1, 2, 3, 4),
- 'dict' : {'A': 1, 'B' : 2},
- 'function' : f,
- 'class' : C,
-}
-for (key, value) in stuff.items():
- cache.set(key, value)
- assert cache.get(key) == value
-
-# expiration
-cache.set('expire', 'very quickly', 1)
-time.sleep(2)
-assert cache.get("expire") == None
diff --git a/tests/othertests/markup.py b/tests/othertests/markup.py
deleted file mode 100644
index 3fa5a3a883..0000000000
--- a/tests/othertests/markup.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# Quick tests for the markup templatetags (django.contrib.markup)
-
-from django.template import Template, Context, add_to_builtins
-
-add_to_builtins('django.contrib.markup.templatetags.markup')
-
-# find out if markup modules are installed and tailor the test appropriately
-try:
- import textile
-except ImportError:
- textile = None
-
-try:
- import markdown
-except ImportError:
- markdown = None
-
-try:
- import docutils
-except ImportError:
- docutils = None
-
-# simple examples 'cause this isn't actually testing the markup, just
-# that the filters work as advertised
-
-### test textile
-
-textile_content = """Paragraph 1
-
-Paragraph 2 with "quotes" and @code@"""
-
-t = Template("{{ textile_content|textile }}")
-rendered = t.render(Context(locals())).strip()
-if textile:
- assert rendered == """<p>Paragraph 1</p>
-
-<p>Paragraph 2 with &#8220;quotes&#8221; and <code>code</code></p>"""
-else:
- assert rendered == textile_content
-
-### test markdown
-
-markdown_content = """Paragraph 1
-
-## An h2"""
-
-t = Template("{{ markdown_content|markdown }}")
-rendered = t.render(Context(locals())).strip()
-if markdown:
- assert rendered == """<p>Paragraph 1</p><h2>An h2</h2>"""
-else:
- assert rendered == markdown_content
-
-### test rest
-
-rest_content = """Paragraph 1
-
-Paragraph 2 with a link_
-
-.. _link: http://www.example.com/"""
-
-t = Template("{{ rest_content|restructuredtext }}")
-rendered = t.render(Context(locals())).strip()
-if docutils:
- assert rendered =="""<p>Paragraph 1</p>
-<p>Paragraph 2 with a <a class="reference" href="http://www.example.com/">link</a></p>"""
-else:
- assert rendered == rest_content
diff --git a/tests/othertests/templates.py b/tests/othertests/templates.py
deleted file mode 100644
index d3b09c5310..0000000000
--- a/tests/othertests/templates.py
+++ /dev/null
@@ -1,625 +0,0 @@
-from django.conf import settings
-
-
-from django import template
-from django.template import loader
-from django.utils.translation import activate, deactivate, install
-from django.utils.tzinfo import LocalTimezone
-from datetime import datetime, timedelta
-import traceback
-
-#################################
-# Custom template tag for tests #
-#################################
-
-register = template.Library()
-
-class EchoNode(template.Node):
- def __init__(self, contents):
- self.contents = contents
-
- def render(self, context):
- return " ".join(self.contents)
-
-def do_echo(parser, token):
- return EchoNode(token.contents.split()[1:])
-
-register.tag("echo", do_echo)
-
-template.libraries['django.templatetags.testtags'] = register
-
-#####################################
-# Helper objects for template tests #
-#####################################
-
-class SomeException(Exception):
- silent_variable_failure = True
-
-class SomeOtherException(Exception):
- pass
-
-class SomeClass:
- def __init__(self):
- self.otherclass = OtherClass()
-
- def method(self):
- return "SomeClass.method"
-
- def method2(self, o):
- return o
-
- def method3(self):
- raise SomeException
-
- def method4(self):
- raise SomeOtherException
-
-class OtherClass:
- def method(self):
- return "OtherClass.method"
-
-# NOW and NOW_tz are used by timesince tag tests.
-NOW = datetime.now()
-NOW_tz = datetime.now(LocalTimezone(datetime.now()))
-
-# SYNTAX --
-# 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class)
-TEMPLATE_TESTS = {
-
- ### BASIC SYNTAX ##########################################################
-
- # Plain text should go through the template parser untouched
- 'basic-syntax01': ("something cool", {}, "something cool"),
-
- # Variables should be replaced with their value in the current context
- 'basic-syntax02': ("{{ headline }}", {'headline':'Success'}, "Success"),
-
- # More than one replacement variable is allowed in a template
- 'basic-syntax03': ("{{ first }} --- {{ second }}", {"first" : 1, "second" : 2}, "1 --- 2"),
-
- # Fail silently when a variable is not found in the current context
- 'basic-syntax04': ("as{{ missing }}df", {}, "asINVALIDdf"),
-
- # A variable may not contain more than one word
- 'basic-syntax06': ("{{ multi word variable }}", {}, template.TemplateSyntaxError),
-
- # Raise TemplateSyntaxError for empty variable tags
- 'basic-syntax07': ("{{ }}", {}, template.TemplateSyntaxError),
- 'basic-syntax08': ("{{ }}", {}, template.TemplateSyntaxError),
-
- # Attribute syntax allows a template to call an object's attribute
- 'basic-syntax09': ("{{ var.method }}", {"var": SomeClass()}, "SomeClass.method"),
-
- # Multiple levels of attribute access are allowed
- 'basic-syntax10': ("{{ var.otherclass.method }}", {"var": SomeClass()}, "OtherClass.method"),
-
- # Fail silently when a variable's attribute isn't found
- 'basic-syntax11': ("{{ var.blech }}", {"var": SomeClass()}, "INVALID"),
-
- # Raise TemplateSyntaxError when trying to access a variable beginning with an underscore
- 'basic-syntax12': ("{{ var.__dict__ }}", {"var": SomeClass()}, template.TemplateSyntaxError),
-
- # Raise TemplateSyntaxError when trying to access a variable containing an illegal character
- 'basic-syntax13': ("{{ va>r }}", {}, template.TemplateSyntaxError),
- 'basic-syntax14': ("{{ (var.r) }}", {}, template.TemplateSyntaxError),
- 'basic-syntax15': ("{{ sp%am }}", {}, template.TemplateSyntaxError),
- 'basic-syntax16': ("{{ eggs! }}", {}, template.TemplateSyntaxError),
- 'basic-syntax17': ("{{ moo? }}", {}, template.TemplateSyntaxError),
-
- # Attribute syntax allows a template to call a dictionary key's value
- 'basic-syntax18': ("{{ foo.bar }}", {"foo" : {"bar" : "baz"}}, "baz"),
-
- # Fail silently when a variable's dictionary key isn't found
- 'basic-syntax19': ("{{ foo.spam }}", {"foo" : {"bar" : "baz"}}, "INVALID"),
-
- # Fail silently when accessing a non-simple method
- 'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, "INVALID"),
-
- # Basic filter usage
- 'basic-syntax21': ("{{ var|upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"),
-
- # Chained filters
- 'basic-syntax22': ("{{ var|upper|lower }}", {"var": "Django is the greatest!"}, "django is the greatest!"),
-
- # Raise TemplateSyntaxError for space between a variable and filter pipe
- 'basic-syntax23': ("{{ var |upper }}", {}, template.TemplateSyntaxError),
-
- # Raise TemplateSyntaxError for space after a filter pipe
- 'basic-syntax24': ("{{ var| upper }}", {}, template.TemplateSyntaxError),
-
- # Raise TemplateSyntaxError for a nonexistent filter
- 'basic-syntax25': ("{{ var|does_not_exist }}", {}, template.TemplateSyntaxError),
-
- # Raise TemplateSyntaxError when trying to access a filter containing an illegal character
- 'basic-syntax26': ("{{ var|fil(ter) }}", {}, template.TemplateSyntaxError),
-
- # Raise TemplateSyntaxError for invalid block tags
- 'basic-syntax27': ("{% nothing_to_see_here %}", {}, template.TemplateSyntaxError),
-
- # Raise TemplateSyntaxError for empty block tags
- 'basic-syntax28': ("{% %}", {}, template.TemplateSyntaxError),
-
- # Chained filters, with an argument to the first one
- 'basic-syntax29': ('{{ var|removetags:"b i"|upper|lower }}', {"var": "<b><i>Yes</i></b>"}, "yes"),
-
- # Escaped string as argument
- 'basic-syntax30': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote" hah'),
-
- # Variable as argument
- 'basic-syntax31': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'),
-
- # Default argument testing
- 'basic-syntax32': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'),
-
- # Fail silently for methods that raise an exception with a "silent_variable_failure" attribute
- 'basic-syntax33': (r'1{{ var.method3 }}2', {"var": SomeClass()}, "1INVALID2"),
-
- # In methods that raise an exception without a "silent_variable_attribute" set to True,
- # the exception propogates
- 'basic-syntax34': (r'1{{ var.method4 }}2', {"var": SomeClass()}, SomeOtherException),
-
- # Escaped backslash in argument
- 'basic-syntax35': (r'{{ var|default_if_none:"foo\bar" }}', {"var": None}, r'foo\bar'),
-
- # Escaped backslash using known escape char
- 'basic-syntax35': (r'{{ var|default_if_none:"foo\now" }}', {"var": None}, r'foo\now'),
-
- ### COMMENT TAG ###########################################################
- 'comment-tag01': ("{% comment %}this is hidden{% endcomment %}hello", {}, "hello"),
- 'comment-tag02': ("{% comment %}this is hidden{% endcomment %}hello{% comment %}foo{% endcomment %}", {}, "hello"),
-
- # Comment tag can contain invalid stuff.
- 'comment-tag03': ("foo{% comment %} {% if %} {% endcomment %}", {}, "foo"),
- 'comment-tag04': ("foo{% comment %} {% endblock %} {% endcomment %}", {}, "foo"),
- 'comment-tag05': ("foo{% comment %} {% somerandomtag %} {% endcomment %}", {}, "foo"),
-
- ### CYCLE TAG #############################################################
- 'cycle01': ('{% cycle a %}', {}, template.TemplateSyntaxError),
- 'cycle02': ('{% cycle a,b,c as abc %}{% cycle abc %}', {}, 'ab'),
- 'cycle03': ('{% cycle a,b,c as abc %}{% cycle abc %}{% cycle abc %}', {}, 'abc'),
- 'cycle04': ('{% cycle a,b,c as abc %}{% cycle abc %}{% cycle abc %}{% cycle abc %}', {}, 'abca'),
- 'cycle05': ('{% cycle %}', {}, template.TemplateSyntaxError),
- 'cycle06': ('{% cycle a %}', {}, template.TemplateSyntaxError),
- 'cycle07': ('{% cycle a,b,c as foo %}{% cycle bar %}', {}, template.TemplateSyntaxError),
-
- ### EXCEPTIONS ############################################################
-
- # Raise exception for invalid template name
- 'exception01': ("{% extends 'nonexistent' %}", {}, template.TemplateSyntaxError),
-
- # Raise exception for invalid template name (in variable)
- 'exception02': ("{% extends nonexistent %}", {}, template.TemplateSyntaxError),
-
- # Raise exception for extra {% extends %} tags
- 'exception03': ("{% extends 'inheritance01' %}{% block first %}2{% endblock %}{% extends 'inheritance16' %}", {}, template.TemplateSyntaxError),
-
- # Raise exception for custom tags used in child with {% load %} tag in parent, not in child
- 'exception04': ("{% extends 'inheritance17' %}{% block first %}{% echo 400 %}5678{% endblock %}", {}, template.TemplateSyntaxError),
-
- ### FILTER TAG ############################################################
- 'filter01': ('{% filter upper %}{% endfilter %}', {}, ''),
- 'filter02': ('{% filter upper %}django{% endfilter %}', {}, 'DJANGO'),
- 'filter03': ('{% filter upper|lower %}django{% endfilter %}', {}, 'django'),
-
- ### FIRSTOF TAG ###########################################################
- 'firstof01': ('{% firstof a b c %}', {'a':0,'b':0,'c':0}, ''),
- 'firstof02': ('{% firstof a b c %}', {'a':1,'b':0,'c':0}, '1'),
- 'firstof03': ('{% firstof a b c %}', {'a':0,'b':2,'c':0}, '2'),
- 'firstof04': ('{% firstof a b c %}', {'a':0,'b':0,'c':3}, '3'),
- 'firstof05': ('{% firstof a b c %}', {'a':1,'b':2,'c':3}, '1'),
- 'firstof06': ('{% firstof %}', {}, template.TemplateSyntaxError),
-
- ### FOR TAG ###############################################################
- 'for-tag01': ("{% for val in values %}{{ val }}{% endfor %}", {"values": [1, 2, 3]}, "123"),
- 'for-tag02': ("{% for val in values reversed %}{{ val }}{% endfor %}", {"values": [1, 2, 3]}, "321"),
- 'for-tag-vars01': ("{% for val in values %}{{ forloop.counter }}{% endfor %}", {"values": [6, 6, 6]}, "123"),
- 'for-tag-vars02': ("{% for val in values %}{{ forloop.counter0 }}{% endfor %}", {"values": [6, 6, 6]}, "012"),
- 'for-tag-vars03': ("{% for val in values %}{{ forloop.revcounter }}{% endfor %}", {"values": [6, 6, 6]}, "321"),
- 'for-tag-vars04': ("{% for val in values %}{{ forloop.revcounter0 }}{% endfor %}", {"values": [6, 6, 6]}, "210"),
-
- ### IF TAG ################################################################
- 'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),
- 'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"),
- 'if-tag03': ("{% if foo %}yes{% else %}no{% endif %}", {}, "no"),
-
- # AND
- 'if-tag-and01': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
- 'if-tag-and02': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
- 'if-tag-and03': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
- 'if-tag-and04': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
- 'if-tag-and05': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': False}, 'no'),
- 'if-tag-and06': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'bar': False}, 'no'),
- 'if-tag-and07': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True}, 'no'),
- 'if-tag-and08': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'bar': True}, 'no'),
-
- # OR
- 'if-tag-or01': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
- 'if-tag-or02': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
- 'if-tag-or03': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
- 'if-tag-or04': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
- 'if-tag-or05': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': False}, 'no'),
- 'if-tag-or06': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'bar': False}, 'no'),
- 'if-tag-or07': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True}, 'yes'),
- 'if-tag-or08': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'bar': True}, 'yes'),
-
- # TODO: multiple ORs
-
- # NOT
- 'if-tag-not01': ("{% if not foo %}no{% else %}yes{% endif %}", {'foo': True}, 'yes'),
- 'if-tag-not02': ("{% if not %}yes{% else %}no{% endif %}", {'foo': True}, 'no'),
- 'if-tag-not03': ("{% if not %}yes{% else %}no{% endif %}", {'not': True}, 'yes'),
- 'if-tag-not04': ("{% if not not %}no{% else %}yes{% endif %}", {'not': True}, 'yes'),
- 'if-tag-not05': ("{% if not not %}no{% else %}yes{% endif %}", {}, 'no'),
-
- 'if-tag-not06': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {}, 'no'),
- 'if-tag-not07': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
- 'if-tag-not08': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
- 'if-tag-not09': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
- 'if-tag-not10': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
-
- 'if-tag-not11': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {}, 'no'),
- 'if-tag-not12': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
- 'if-tag-not13': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
- 'if-tag-not14': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
- 'if-tag-not15': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
-
- 'if-tag-not16': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {}, 'yes'),
- 'if-tag-not17': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
- 'if-tag-not18': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
- 'if-tag-not19': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
- 'if-tag-not20': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
-
- 'if-tag-not21': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {}, 'yes'),
- 'if-tag-not22': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
- 'if-tag-not23': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
- 'if-tag-not24': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
- 'if-tag-not25': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
-
- 'if-tag-not26': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {}, 'yes'),
- 'if-tag-not27': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
- 'if-tag-not28': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
- 'if-tag-not29': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
- 'if-tag-not30': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
-
- 'if-tag-not31': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {}, 'yes'),
- 'if-tag-not32': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
- 'if-tag-not33': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
- 'if-tag-not34': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
- 'if-tag-not35': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
-
- # AND and OR raises a TemplateSyntaxError
- 'if-tag-error01': ("{% if foo or bar and baz %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, template.TemplateSyntaxError),
- 'if-tag-error02': ("{% if foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
- 'if-tag-error03': ("{% if foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
- 'if-tag-error04': ("{% if not foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
- 'if-tag-error05': ("{% if not foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
-
- ### IFCHANGED TAG #########################################################
- 'ifchanged01': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,2,3) }, '123'),
- 'ifchanged02': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,3) }, '13'),
- 'ifchanged03': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,1) }, '1'),
-
- ### IFEQUAL TAG ###########################################################
- 'ifequal01': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 2}, ""),
- 'ifequal02': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 1}, "yes"),
- 'ifequal03': ("{% ifequal a b %}yes{% else %}no{% endifequal %}", {"a": 1, "b": 2}, "no"),
- 'ifequal04': ("{% ifequal a b %}yes{% else %}no{% endifequal %}", {"a": 1, "b": 1}, "yes"),
- 'ifequal05': ("{% ifequal a 'test' %}yes{% else %}no{% endifequal %}", {"a": "test"}, "yes"),
- 'ifequal06': ("{% ifequal a 'test' %}yes{% else %}no{% endifequal %}", {"a": "no"}, "no"),
- 'ifequal07': ('{% ifequal a "test" %}yes{% else %}no{% endifequal %}', {"a": "test"}, "yes"),
- 'ifequal08': ('{% ifequal a "test" %}yes{% else %}no{% endifequal %}', {"a": "no"}, "no"),
- 'ifequal09': ('{% ifequal a "test" %}yes{% else %}no{% endifequal %}', {}, "no"),
- 'ifequal10': ('{% ifequal a b %}yes{% else %}no{% endifequal %}', {}, "yes"),
-
- # SMART SPLITTING
- 'ifequal-split01': ('{% ifequal a "test man" %}yes{% else %}no{% endifequal %}', {}, "no"),
- 'ifequal-split02': ('{% ifequal a "test man" %}yes{% else %}no{% endifequal %}', {'a': 'foo'}, "no"),
- 'ifequal-split03': ('{% ifequal a "test man" %}yes{% else %}no{% endifequal %}', {'a': 'test man'}, "yes"),
- 'ifequal-split04': ("{% ifequal a 'test man' %}yes{% else %}no{% endifequal %}", {'a': 'test man'}, "yes"),
- 'ifequal-split05': ("{% ifequal a 'i \"love\" you' %}yes{% else %}no{% endifequal %}", {'a': ''}, "no"),
- 'ifequal-split06': ("{% ifequal a 'i \"love\" you' %}yes{% else %}no{% endifequal %}", {'a': 'i "love" you'}, "yes"),
- 'ifequal-split07': ("{% ifequal a 'i \"love\" you' %}yes{% else %}no{% endifequal %}", {'a': 'i love you'}, "no"),
- 'ifequal-split08': (r"{% ifequal a 'I\'m happy' %}yes{% else %}no{% endifequal %}", {'a': "I'm happy"}, "yes"),
- 'ifequal-split09': (r"{% ifequal a 'slash\man' %}yes{% else %}no{% endifequal %}", {'a': r"slash\man"}, "yes"),
- 'ifequal-split10': (r"{% ifequal a 'slash\man' %}yes{% else %}no{% endifequal %}", {'a': r"slashman"}, "no"),
-
- ### IFNOTEQUAL TAG ########################################################
- 'ifnotequal01': ("{% ifnotequal a b %}yes{% endifnotequal %}", {"a": 1, "b": 2}, "yes"),
- 'ifnotequal02': ("{% ifnotequal a b %}yes{% endifnotequal %}", {"a": 1, "b": 1}, ""),
- 'ifnotequal03': ("{% ifnotequal a b %}yes{% else %}no{% endifnotequal %}", {"a": 1, "b": 2}, "yes"),
- 'ifnotequal04': ("{% ifnotequal a b %}yes{% else %}no{% endifnotequal %}", {"a": 1, "b": 1}, "no"),
-
- ### INCLUDE TAG ###########################################################
- 'include01': ('{% include "basic-syntax01" %}', {}, "something cool"),
- 'include02': ('{% include "basic-syntax02" %}', {'headline': 'Included'}, "Included"),
- 'include03': ('{% include template_name %}', {'template_name': 'basic-syntax02', 'headline': 'Included'}, "Included"),
- 'include04': ('a{% include "nonexistent" %}b', {}, "ab"),
-
- ### INHERITANCE ###########################################################
-
- # Standard template with no inheritance
- 'inheritance01': ("1{% block first %}_{% endblock %}3{% block second %}_{% endblock %}", {}, '1_3_'),
-
- # Standard two-level inheritance
- 'inheritance02': ("{% extends 'inheritance01' %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {}, '1234'),
-
- # Three-level with no redefinitions on third level
- 'inheritance03': ("{% extends 'inheritance02' %}", {}, '1234'),
-
- # Two-level with no redefinitions on second level
- 'inheritance04': ("{% extends 'inheritance01' %}", {}, '1_3_'),
-
- # Two-level with double quotes instead of single quotes
- 'inheritance05': ('{% extends "inheritance02" %}', {}, '1234'),
-
- # Three-level with variable parent-template name
- 'inheritance06': ("{% extends foo %}", {'foo': 'inheritance02'}, '1234'),
-
- # Two-level with one block defined, one block not defined
- 'inheritance07': ("{% extends 'inheritance01' %}{% block second %}5{% endblock %}", {}, '1_35'),
-
- # Three-level with one block defined on this level, two blocks defined next level
- 'inheritance08': ("{% extends 'inheritance02' %}{% block second %}5{% endblock %}", {}, '1235'),
-
- # Three-level with second and third levels blank
- 'inheritance09': ("{% extends 'inheritance04' %}", {}, '1_3_'),
-
- # Three-level with space NOT in a block -- should be ignored
- 'inheritance10': ("{% extends 'inheritance04' %} ", {}, '1_3_'),
-
- # Three-level with both blocks defined on this level, but none on second level
- 'inheritance11': ("{% extends 'inheritance04' %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {}, '1234'),
-
- # Three-level with this level providing one and second level providing the other
- 'inheritance12': ("{% extends 'inheritance07' %}{% block first %}2{% endblock %}", {}, '1235'),
-
- # Three-level with this level overriding second level
- 'inheritance13': ("{% extends 'inheritance02' %}{% block first %}a{% endblock %}{% block second %}b{% endblock %}", {}, '1a3b'),
-
- # A block defined only in a child template shouldn't be displayed
- 'inheritance14': ("{% extends 'inheritance01' %}{% block newblock %}NO DISPLAY{% endblock %}", {}, '1_3_'),
-
- # A block within another block
- 'inheritance15': ("{% extends 'inheritance01' %}{% block first %}2{% block inner %}inner{% endblock %}{% endblock %}", {}, '12inner3_'),
-
- # A block within another block (level 2)
- 'inheritance16': ("{% extends 'inheritance15' %}{% block inner %}out{% endblock %}", {}, '12out3_'),
-
- # {% load %} tag (parent -- setup for exception04)
- 'inheritance17': ("{% load testtags %}{% block first %}1234{% endblock %}", {}, '1234'),
-
- # {% load %} tag (standard usage, without inheritance)
- 'inheritance18': ("{% load testtags %}{% echo this that theother %}5678", {}, 'this that theother5678'),
-
- # {% load %} tag (within a child template)
- 'inheritance19': ("{% extends 'inheritance01' %}{% block first %}{% load testtags %}{% echo 400 %}5678{% endblock %}", {}, '140056783_'),
-
- # Two-level inheritance with {{ block.super }}
- 'inheritance20': ("{% extends 'inheritance01' %}{% block first %}{{ block.super }}a{% endblock %}", {}, '1_a3_'),
-
- # Three-level inheritance with {{ block.super }} from parent
- 'inheritance21': ("{% extends 'inheritance02' %}{% block first %}{{ block.super }}a{% endblock %}", {}, '12a34'),
-
- # Three-level inheritance with {{ block.super }} from grandparent
- 'inheritance22': ("{% extends 'inheritance04' %}{% block first %}{{ block.super }}a{% endblock %}", {}, '1_a3_'),
-
- # Three-level inheritance with {{ block.super }} from parent and grandparent
- 'inheritance23': ("{% extends 'inheritance20' %}{% block first %}{{ block.super }}b{% endblock %}", {}, '1_ab3_'),
-
- ### I18N ##################################################################
-
- # {% spaceless %} tag
- 'spaceless01': ("{% spaceless %} <b> <i> text </i> </b> {% endspaceless %}", {}, "<b> <i> text </i> </b>"),
- 'spaceless02': ("{% spaceless %} <b> \n <i> text </i> \n </b> {% endspaceless %}", {}, "<b> <i> text </i> </b>"),
- 'spaceless03': ("{% spaceless %}<b><i>text</i></b>{% endspaceless %}", {}, "<b><i>text</i></b>"),
-
- # simple translation of a string delimited by '
- 'i18n01': ("{% load i18n %}{% trans 'xxxyyyxxx' %}", {}, "xxxyyyxxx"),
-
- # simple translation of a string delimited by "
- 'i18n02': ('{% load i18n %}{% trans "xxxyyyxxx" %}', {}, "xxxyyyxxx"),
-
- # simple translation of a variable
- 'i18n03': ('{% load i18n %}{% blocktrans %}{{ anton }}{% endblocktrans %}', {'anton': 'xxxyyyxxx'}, "xxxyyyxxx"),
-
- # simple translation of a variable and filter
- 'i18n04': ('{% load i18n %}{% blocktrans with anton|lower as berta %}{{ berta }}{% endblocktrans %}', {'anton': 'XXXYYYXXX'}, "xxxyyyxxx"),
-
- # simple translation of a string with interpolation
- 'i18n05': ('{% load i18n %}{% blocktrans %}xxx{{ anton }}xxx{% endblocktrans %}', {'anton': 'yyy'}, "xxxyyyxxx"),
-
- # simple translation of a string to german
- 'i18n06': ('{% load i18n %}{% trans "Page not found" %}', {'LANGUAGE_CODE': 'de'}, "Seite nicht gefunden"),
-
- # translation of singular form
- 'i18n07': ('{% load i18n %}{% blocktrans count number as counter %}singular{% plural %}plural{% endblocktrans %}', {'number': 1}, "singular"),
-
- # translation of plural form
- 'i18n08': ('{% load i18n %}{% blocktrans count number as counter %}singular{% plural %}plural{% endblocktrans %}', {'number': 2}, "plural"),
-
- # simple non-translation (only marking) of a string to german
- 'i18n09': ('{% load i18n %}{% trans "Page not found" noop %}', {'LANGUAGE_CODE': 'de'}, "Page not found"),
-
- # translation of a variable with a translated filter
- 'i18n10': ('{{ bool|yesno:_("ja,nein") }}', {'bool': True}, 'ja'),
-
- # translation of a variable with a non-translated filter
- 'i18n11': ('{{ bool|yesno:"ja,nein" }}', {'bool': True}, 'ja'),
-
- # usage of the get_available_languages tag
- 'i18n12': ('{% load i18n %}{% get_available_languages as langs %}{% for lang in langs %}{% ifequal lang.0 "de" %}{{ lang.0 }}{% endifequal %}{% endfor %}', {}, 'de'),
-
- # translation of a constant string
- 'i18n13': ('{{ _("Page not found") }}', {'LANGUAGE_CODE': 'de'}, 'Seite nicht gefunden'),
-
- ### MULTILINE #############################################################
-
- 'multiline01': ("""
- Hello,
- boys.
- How
- are
- you
- gentlemen.
- """,
- {},
- """
- Hello,
- boys.
- How
- are
- you
- gentlemen.
- """),
-
- ### REGROUP TAG ###########################################################
- 'regroup01': ('{% regroup data by bar as grouped %}' + \
- '{% for group in grouped %}' + \
- '{{ group.grouper }}:' + \
- '{% for item in group.list %}' + \
- '{{ item.foo }}' + \
- '{% endfor %},' + \
- '{% endfor %}',
- {'data': [ {'foo':'c', 'bar':1},
- {'foo':'d', 'bar':1},
- {'foo':'a', 'bar':2},
- {'foo':'b', 'bar':2},
- {'foo':'x', 'bar':3} ]},
- '1:cd,2:ab,3:x,'),
-
- # Test for silent failure when target variable isn't found
- 'regroup02': ('{% regroup data by bar as grouped %}' + \
- '{% for group in grouped %}' + \
- '{{ group.grouper }}:' + \
- '{% for item in group.list %}' + \
- '{{ item.foo }}' + \
- '{% endfor %},' + \
- '{% endfor %}',
- {}, 'INVALID:INVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALID,'),
-
- ### TEMPLATETAG TAG #######################################################
- 'templatetag01': ('{% templatetag openblock %}', {}, '{%'),
- 'templatetag02': ('{% templatetag closeblock %}', {}, '%}'),
- 'templatetag03': ('{% templatetag openvariable %}', {}, '{{'),
- 'templatetag04': ('{% templatetag closevariable %}', {}, '}}'),
- 'templatetag05': ('{% templatetag %}', {}, template.TemplateSyntaxError),
- 'templatetag06': ('{% templatetag foo %}', {}, template.TemplateSyntaxError),
- 'templatetag07': ('{% templatetag openbrace %}', {}, '{'),
- 'templatetag08': ('{% templatetag closebrace %}', {}, '}'),
- 'templatetag09': ('{% templatetag openbrace %}{% templatetag openbrace %}', {}, '{{'),
- 'templatetag10': ('{% templatetag closebrace %}{% templatetag closebrace %}', {}, '}}'),
-
- ### WIDTHRATIO TAG ########################################################
- 'widthratio01': ('{% widthratio a b 0 %}', {'a':50,'b':100}, '0'),
- 'widthratio02': ('{% widthratio a b 100 %}', {'a':0,'b':0}, ''),
- 'widthratio03': ('{% widthratio a b 100 %}', {'a':0,'b':100}, '0'),
- 'widthratio04': ('{% widthratio a b 100 %}', {'a':50,'b':100}, '50'),
- 'widthratio05': ('{% widthratio a b 100 %}', {'a':100,'b':100}, '100'),
-
- # 62.5 should round to 63
- 'widthratio06': ('{% widthratio a b 100 %}', {'a':50,'b':80}, '63'),
-
- # 71.4 should round to 71
- 'widthratio07': ('{% widthratio a b 100 %}', {'a':50,'b':70}, '71'),
-
- # Raise exception if we don't have 3 args, last one an integer
- 'widthratio08': ('{% widthratio %}', {}, template.TemplateSyntaxError),
- 'widthratio09': ('{% widthratio a b %}', {'a':50,'b':100}, template.TemplateSyntaxError),
- 'widthratio10': ('{% widthratio a b 100.0 %}', {'a':50,'b':100}, template.TemplateSyntaxError),
-
- ### NOW TAG ########################################################
- # Simple case
- 'now01' : ('{% now "j n Y"%}', {}, str(datetime.now().day) + ' ' + str(datetime.now().month) + ' ' + str(datetime.now().year)),
-
- # Check parsing of escaped and special characters
- 'now02' : ('{% now "j "n" Y"%}', {}, template.TemplateSyntaxError),
-# 'now03' : ('{% now "j \"n\" Y"%}', {}, str(datetime.now().day) + '"' + str(datetime.now().month) + '"' + str(datetime.now().year)),
-# 'now04' : ('{% now "j \nn\n Y"%}', {}, str(datetime.now().day) + '\n' + str(datetime.now().month) + '\n' + str(datetime.now().year))
-
- ### TIMESINCE TAG ##################################################
- # Default compare with datetime.now()
- 'timesince01' : ('{{ a|timesince }}', {'a':datetime.now() + timedelta(minutes=-1)}, '1 minute'),
- 'timesince02' : ('{{ a|timesince }}', {'a':(datetime.now() - timedelta(days=1))}, '1 day'),
- 'timesince03' : ('{{ a|timesince }}', {'a':(datetime.now() -
- timedelta(hours=1, minutes=25))}, '1 hour, 25 minutes'),
-
- # Compare to a given parameter
- 'timesince04' : ('{{ a|timesince:b }}', {'a':NOW + timedelta(days=2), 'b':NOW + timedelta(days=1)}, '1 day'),
- 'timesince05' : ('{{ a|timesince:b }}', {'a':NOW + timedelta(days=2, minutes=1), 'b':NOW + timedelta(days=2)}, '1 minute'),
-
- # Check that timezone is respected
- 'timesince06' : ('{{ a|timesince:b }}', {'a':NOW_tz + timedelta(hours=8), 'b':NOW_tz}, '8 hours'),
-
- ### TIMEUNTIL TAG ##################################################
- # Default compare with datetime.now()
- 'timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2)}, '2 minutes'),
- 'timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1))}, '1 day'),
- 'timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10))}, '8 hours, 10 minutes'),
-
- # Compare to a given parameter
- 'timeuntil04' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=1), 'b':NOW - timedelta(days=2)}, '1 day'),
- 'timeuntil05' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=2), 'b':NOW - timedelta(days=2, minutes=1)}, '1 minute'),
-}
-
-def test_template_loader(template_name, template_dirs=None):
- "A custom template loader that loads the unit-test templates."
- try:
- return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name)
- except KeyError:
- raise template.TemplateDoesNotExist, template_name
-
-def run_tests(verbosity=0, standalone=False):
- # Register our custom template loader.
- old_template_loaders = loader.template_source_loaders
- loader.template_source_loaders = [test_template_loader]
-
- failed_tests = []
- tests = TEMPLATE_TESTS.items()
- tests.sort()
-
- # Turn TEMPLATE_DEBUG off, because tests assume that.
- old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
- # Set TEMPLATE_STRING_IF_INVALID to a known string
- old_invalid, settings.TEMPLATE_STRING_IF_INVALID = settings.TEMPLATE_STRING_IF_INVALID, 'INVALID'
-
- for name, vals in tests:
- install()
- if 'LANGUAGE_CODE' in vals[1]:
- activate(vals[1]['LANGUAGE_CODE'])
- else:
- activate('en-us')
- try:
- output = loader.get_template(name).render(template.Context(vals[1]))
- except Exception, e:
- if e.__class__ == vals[2]:
- if verbosity:
- print "Template test: %s -- Passed" % name
- else:
- if verbosity:
- traceback.print_exc()
- print "Template test: %s -- FAILED. Got %s, exception: %s" % (name, e.__class__, e)
- failed_tests.append(name)
- continue
- if 'LANGUAGE_CODE' in vals[1]:
- deactivate()
- if output == vals[2]:
- if verbosity:
- print "Template test: %s -- Passed" % name
- else:
- if verbosity:
- print "Template test: %s -- FAILED. Expected %r, got %r" % (name, vals[2], output)
- failed_tests.append(name)
- loader.template_source_loaders = old_template_loaders
- deactivate()
- settings.TEMPLATE_DEBUG = old_td
- settings.TEMPLATE_STRING_IF_INVALID = old_invalid
-
- if failed_tests and not standalone:
- msg = "Template tests %s failed." % failed_tests
- if not verbosity:
- msg += " Re-run tests with -v1 to see actual failures"
- raise Exception, msg
-
-if __name__ == "__main__":
- settings.configure()
- run_tests(1, True)
diff --git a/tests/regressiontests/cache/__init__.py b/tests/regressiontests/cache/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/cache/__init__.py
diff --git a/tests/regressiontests/cache/models.py b/tests/regressiontests/cache/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/cache/models.py
diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py
new file mode 100644
index 0000000000..cf58ab321a
--- /dev/null
+++ b/tests/regressiontests/cache/tests.py
@@ -0,0 +1,71 @@
+# Unit tests for cache framework
+# Uses whatever cache backend is set in the test settings file.
+
+from django.core.cache import cache
+import time, unittest
+
+# functions/classes for complex data type tests
+def f():
+ return 42
+class C:
+ def m(n):
+ return 24
+
+class Cache(unittest.TestCase):
+ def test_simple(self):
+ # simple set/get
+ cache.set("key", "value")
+ self.assertEqual(cache.get("key"), "value")
+
+ def test_non_existent(self):
+ # get with non-existent keys
+ self.assertEqual(cache.get("does not exist"), None)
+ self.assertEqual(cache.get("does not exist", "bang!"), "bang!")
+
+ def test_get_many(self):
+ # get_many
+ cache.set('a', 'a')
+ cache.set('b', 'b')
+ cache.set('c', 'c')
+ cache.set('d', 'd')
+ self.assertEqual(cache.get_many(['a', 'c', 'd']), {'a' : 'a', 'c' : 'c', 'd' : 'd'})
+ self.assertEqual(cache.get_many(['a', 'b', 'e']), {'a' : 'a', 'b' : 'b'})
+
+ def test_delete(self):
+ # delete
+ cache.set("key1", "spam")
+ cache.set("key2", "eggs")
+ self.assertEqual(cache.get("key1"), "spam")
+ cache.delete("key1")
+ self.assertEqual(cache.get("key1"), None)
+ self.assertEqual(cache.get("key2"), "eggs")
+
+ def test_has_key(self):
+ # has_key
+ cache.set("hello", "goodbye")
+ self.assertEqual(cache.has_key("hello"), True)
+ self.assertEqual(cache.has_key("goodbye"), False)
+
+ def test_data_types(self):
+ # test data types
+ stuff = {
+ 'string' : 'this is a string',
+ 'int' : 42,
+ 'list' : [1, 2, 3, 4],
+ 'tuple' : (1, 2, 3, 4),
+ 'dict' : {'A': 1, 'B' : 2},
+ 'function' : f,
+ 'class' : C,
+ }
+ for (key, value) in stuff.items():
+ cache.set(key, value)
+ self.assertEqual(cache.get(key), value)
+
+ def test_expiration(self):
+ # expiration
+ cache.set('expire', 'very quickly', 1)
+ time.sleep(2)
+ self.assertEqual(cache.get("expire"), None)
+
+if __name__ == '__main__':
+ unittest.main() \ No newline at end of file
diff --git a/tests/regressiontests/dateformat/__init__.py b/tests/regressiontests/dateformat/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/dateformat/__init__.py
diff --git a/tests/regressiontests/dateformat/models.py b/tests/regressiontests/dateformat/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/dateformat/models.py
diff --git a/tests/othertests/dateformat.py b/tests/regressiontests/dateformat/tests.py
index 0287587b4a..6e28759a92 100644
--- a/tests/othertests/dateformat.py
+++ b/tests/regressiontests/dateformat/tests.py
@@ -21,22 +21,22 @@ r"""
'7'
>>> format(my_birthday, 'N')
'July'
->>> format(my_birthday, 'O')
-'+0100'
+>>> no_tz or format(my_birthday, 'O') == '+0100'
+True
>>> format(my_birthday, 'P')
'10 p.m.'
->>> format(my_birthday, 'r')
-'Sun, 8 Jul 1979 22:00:00 +0100'
+>>> no_tz or format(my_birthday, 'r') == 'Sun, 8 Jul 1979 22:00:00 +0100'
+True
>>> format(my_birthday, 's')
'00'
>>> format(my_birthday, 'S')
'th'
>>> format(my_birthday, 't')
'31'
->>> format(my_birthday, 'T')
-'CET'
->>> format(my_birthday, 'U')
-'300531600'
+>>> no_tz or format(my_birthday, 'T') == 'CET'
+True
+>>> no_tz or format(my_birthday, 'U') == '300531600'
+True
>>> format(my_birthday, 'w')
'0'
>>> format(my_birthday, 'W')
@@ -47,17 +47,17 @@ r"""
'1979'
>>> format(my_birthday, 'z')
'189'
->>> format(my_birthday, 'Z')
-'3600'
+>>> no_tz or format(my_birthday, 'Z') == '3600'
+True
->>> format(summertime, 'I')
-'1'
->>> format(summertime, 'O')
-'+0200'
->>> format(wintertime, 'I')
-'0'
->>> format(wintertime, 'O')
-'+0100'
+>>> no_tz or format(summertime, 'I') == '1'
+True
+>>> no_tz or format(summertime, 'O') == '+0200'
+True
+>>> no_tz or format(wintertime, 'I') == '0'
+True
+>>> no_tz or format(wintertime, 'O') == '+0100'
+True
>>> format(my_birthday, r'Y z \C\E\T')
'1979 189 CET'
@@ -73,7 +73,11 @@ format = dateformat.format
os.environ['TZ'] = 'Europe/Copenhagen'
translation.activate('en-us')
-time.tzset()
+try:
+ time.tzset()
+ no_tz = False
+except AttributeError:
+ no_tz = True
my_birthday = datetime.datetime(1979, 7, 8, 22, 00)
summertime = datetime.datetime(2005, 10, 30, 1, 00)
diff --git a/tests/regressiontests/db_typecasts/__init__.py b/tests/regressiontests/db_typecasts/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/db_typecasts/__init__.py
diff --git a/tests/regressiontests/db_typecasts/models.py b/tests/regressiontests/db_typecasts/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/db_typecasts/models.py
diff --git a/tests/othertests/db_typecasts.py b/tests/regressiontests/db_typecasts/tests.py
index ffc9b34aec..f4b77fe3a6 100644
--- a/tests/othertests/db_typecasts.py
+++ b/tests/regressiontests/db_typecasts/tests.py
@@ -1,7 +1,7 @@
# Unit tests for typecast functions in django.db.backends.util
from django.db.backends import util as typecasts
-import datetime
+import datetime, unittest
TEST_CASES = {
'typecast_date': (
@@ -45,7 +45,12 @@ TEST_CASES = {
),
}
-for k, v in TEST_CASES.items():
- for inpt, expected in v:
- got = getattr(typecasts, k)(inpt)
- assert got == expected, "In %s: %r doesn't match %r. Got %r instead." % (k, inpt, expected, got)
+class DBTypeCasts(unittest.TestCase):
+ def test_typeCasts(self):
+ for k, v in TEST_CASES.items():
+ for inpt, expected in v:
+ got = getattr(typecasts, k)(inpt)
+ assert got == expected, "In %s: %r doesn't match %r. Got %r instead." % (k, inpt, expected, got)
+
+if __name__ == '__main__':
+ unittest.main() \ No newline at end of file
diff --git a/tests/regressiontests/defaultfilters/__init__.py b/tests/regressiontests/defaultfilters/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/defaultfilters/__init__.py
diff --git a/tests/regressiontests/defaultfilters/models.py b/tests/regressiontests/defaultfilters/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/defaultfilters/models.py
diff --git a/tests/othertests/defaultfilters.py b/tests/regressiontests/defaultfilters/tests.py
index 1636b948d0..32d6ef5202 100644
--- a/tests/othertests/defaultfilters.py
+++ b/tests/regressiontests/defaultfilters/tests.py
@@ -15,6 +15,9 @@ r"""
>>> addslashes('"double quotes" and \'single quotes\'')
'\\"double quotes\\" and \\\'single quotes\\\''
+>>> addslashes(r'\ : backslashes, too')
+'\\\\ : backslashes, too'
+
>>> capfirst('hello world')
'Hello world'
@@ -231,6 +234,9 @@ False
>>> time(datetime.time(13), "h")
'01'
+>>> time(datetime.time(0), "h")
+'12'
+
# real testing is done in timesince.py, where we can provide our own 'now'
>>> timesince(datetime.datetime.now() - datetime.timedelta(1))
'1 day'
diff --git a/tests/regressiontests/httpwrappers/__init__.py b/tests/regressiontests/httpwrappers/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/httpwrappers/__init__.py
diff --git a/tests/regressiontests/httpwrappers/models.py b/tests/regressiontests/httpwrappers/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/httpwrappers/models.py
diff --git a/tests/othertests/httpwrappers.py b/tests/regressiontests/httpwrappers/tests.py
index 385c3048d9..385c3048d9 100644
--- a/tests/othertests/httpwrappers.py
+++ b/tests/regressiontests/httpwrappers/tests.py
diff --git a/tests/regressiontests/initial_sql_regress/models.py b/tests/regressiontests/initial_sql_regress/models.py
index c4cf12bdf7..dedbba8e5c 100644
--- a/tests/regressiontests/initial_sql_regress/models.py
+++ b/tests/regressiontests/initial_sql_regress/models.py
@@ -7,7 +7,7 @@ from django.db import models
class Simple(models.Model):
name = models.CharField(maxlength = 50)
-API_TESTS = ""
+__test__ = {'API_TESTS':""}
# NOTE: The format of the included SQL file for this test suite is important.
# It must end with a trailing newline in order to test the fix for #2161.
diff --git a/tests/regressiontests/initial_sql_regress/sql/simple.sql b/tests/regressiontests/initial_sql_regress/sql/simple.sql
index ddb08bc91f..ca9bd40dab 100644
--- a/tests/regressiontests/initial_sql_regress/sql/simple.sql
+++ b/tests/regressiontests/initial_sql_regress/sql/simple.sql
@@ -4,4 +4,5 @@ INSERT INTO initial_sql_regress_simple (name) VALUES ('Ringo');
INSERT INTO initial_sql_regress_simple (name) VALUES ('George');
INSERT INTO initial_sql_regress_simple (name) VALUES ('Miles O''Brien');
INSERT INTO initial_sql_regress_simple (name) VALUES ('Semicolon;Man');
+INSERT INTO initial_sql_regress_simple (name) VALUES ('This line has a Windows line ending');
diff --git a/tests/regressiontests/many_to_one_regress/models.py b/tests/regressiontests/many_to_one_regress/models.py
index 485e928777..6c067446b1 100644
--- a/tests/regressiontests/many_to_one_regress/models.py
+++ b/tests/regressiontests/many_to_one_regress/models.py
@@ -10,4 +10,4 @@ class Second(models.Model):
# created (the field names being lower-cased versions of their opposite
# classes is important here).
-API_TESTS = ""
+__test__ = {'API_TESTS':""}
diff --git a/tests/regressiontests/markup/__init__.py b/tests/regressiontests/markup/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/markup/__init__.py
diff --git a/tests/regressiontests/markup/models.py b/tests/regressiontests/markup/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/markup/models.py
diff --git a/tests/regressiontests/markup/tests.py b/tests/regressiontests/markup/tests.py
new file mode 100644
index 0000000000..bd3f52b9dd
--- /dev/null
+++ b/tests/regressiontests/markup/tests.py
@@ -0,0 +1,69 @@
+# Quick tests for the markup templatetags (django.contrib.markup)
+
+from django.template import Template, Context, add_to_builtins
+import re
+import unittest
+
+add_to_builtins('django.contrib.markup.templatetags.markup')
+
+class Templates(unittest.TestCase):
+ def test_textile(self):
+ try:
+ import textile
+ except ImportError:
+ textile = None
+
+ textile_content = """Paragraph 1
+
+Paragraph 2 with "quotes" and @code@"""
+
+ t = Template("{{ textile_content|textile }}")
+ rendered = t.render(Context(locals())).strip()
+ if textile:
+ self.assertEqual(rendered, """<p>Paragraph 1</p>
+
+<p>Paragraph 2 with &#8220;quotes&#8221; and <code>code</code></p>""")
+ else:
+ self.assertEqual(rendered, textile_content)
+
+ def test_markdown(self):
+ try:
+ import markdown
+ except ImportError:
+ markdown = None
+
+ markdown_content = """Paragraph 1
+
+## An h2"""
+
+ t = Template("{{ markdown_content|markdown }}")
+ rendered = t.render(Context(locals())).strip()
+ if markdown:
+ pattern = re.compile("""<p>Paragraph 1\s*</p>\s*<h2>\s*An h2</h2>""")
+ self.assert_(pattern.match(rendered))
+ else:
+ self.assertEqual(rendered, markdown_content)
+
+ def test_docutils(self):
+ try:
+ import docutils
+ except ImportError:
+ docutils = None
+
+ rest_content = """Paragraph 1
+
+Paragraph 2 with a link_
+
+.. _link: http://www.example.com/"""
+
+ t = Template("{{ rest_content|restructuredtext }}")
+ rendered = t.render(Context(locals())).strip()
+ if docutils:
+ self.assertEqual(rendered, """<p>Paragraph 1</p>
+<p>Paragraph 2 with a <a class="reference" href="http://www.example.com/">link</a></p>""")
+ else:
+ self.assertEqual(rendered, rest_content)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/regressiontests/null_queries/__init__.py b/tests/regressiontests/null_queries/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/null_queries/__init__.py
diff --git a/tests/regressiontests/null_queries/models.py b/tests/regressiontests/null_queries/models.py
new file mode 100644
index 0000000000..09024f18c2
--- /dev/null
+++ b/tests/regressiontests/null_queries/models.py
@@ -0,0 +1,54 @@
+from django.db import models
+
+class Poll(models.Model):
+ question = models.CharField(maxlength=200)
+
+ def __str__(self):
+ return "Q: %s " % self.question
+
+class Choice(models.Model):
+ poll = models.ForeignKey(Poll)
+ choice = models.CharField(maxlength=200)
+
+ def __str__(self):
+ return "Choice: %s in poll %s" % (self.choice, self.poll)
+
+__test__ = {'API_TESTS':"""
+# Regression test for the use of None as a query value. None is interpreted as
+# an SQL NULL, but only in __exact queries.
+# Set up some initial polls and choices
+>>> p1 = Poll(question='Why?')
+>>> p1.save()
+>>> c1 = Choice(poll=p1, choice='Because.')
+>>> c1.save()
+>>> c2 = Choice(poll=p1, choice='Why Not?')
+>>> c2.save()
+
+# Exact query with value None returns nothing (=NULL in sql)
+>>> Choice.objects.filter(id__exact=None)
+[]
+
+# Valid query, but fails because foo isn't a keyword
+>>> Choice.objects.filter(foo__exact=None)
+Traceback (most recent call last):
+...
+TypeError: Cannot resolve keyword 'foo' into field
+
+# Can't use None on anything other than __exact
+>>> Choice.objects.filter(id__gt=None)
+Traceback (most recent call last):
+...
+ValueError: Cannot use None as a query value
+
+# Can't use None on anything other than __exact
+>>> Choice.objects.filter(foo__gt=None)
+Traceback (most recent call last):
+...
+ValueError: Cannot use None as a query value
+
+# Related managers use __exact=None implicitly if the object hasn't been saved.
+>>> p2 = Poll(question="How?")
+>>> p2.choice_set.all()
+[]
+
+"""}
diff --git a/tests/regressiontests/one_to_one_regress/models.py b/tests/regressiontests/one_to_one_regress/models.py
index 6cc1df4e5c..b81f4266e1 100644
--- a/tests/regressiontests/one_to_one_regress/models.py
+++ b/tests/regressiontests/one_to_one_regress/models.py
@@ -22,7 +22,7 @@ class Favorites(models.Model):
def __str__(self):
return "Favorites for %s" % self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Regression test for #1064 and #1506: Check that we create models via the m2m
# relation if the remote model has a OneToOneField.
>>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton')
@@ -34,4 +34,4 @@ API_TESTS = """
>>> f.restaurants = [r]
>>> f.restaurants.all()
[<Restaurant: Demon Dogs the restaurant>]
-"""
+"""}
diff --git a/tests/regressiontests/string_lookup/models.py b/tests/regressiontests/string_lookup/models.py
index a4582ca4e9..441bb3f8a3 100644
--- a/tests/regressiontests/string_lookup/models.py
+++ b/tests/regressiontests/string_lookup/models.py
@@ -34,7 +34,7 @@ class Base(models.Model):
def __str__(self):
return "Base %s" % self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Regression test for #1661 and #1662: Check that string form referencing of models works,
# both as pre and post reference, on all RelatedField types.
@@ -66,4 +66,4 @@ API_TESTS = """
>>> child1.parent
<Base: Base Base1>
-"""
+"""}
diff --git a/tests/regressiontests/templates/__init__.py b/tests/regressiontests/templates/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/templates/__init__.py
diff --git a/tests/regressiontests/templates/models.py b/tests/regressiontests/templates/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/templates/models.py
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
new file mode 100644
index 0000000000..ef31b853ca
--- /dev/null
+++ b/tests/regressiontests/templates/tests.py
@@ -0,0 +1,667 @@
+from django.conf import settings
+
+if __name__ == '__main__':
+ # When running this file in isolation, we need to set up the configuration
+ # before importing 'template'.
+ settings.configure()
+
+from django import template
+from django.template import loader
+from django.utils.translation import activate, deactivate, install
+from django.utils.tzinfo import LocalTimezone
+from datetime import datetime, timedelta
+import unittest
+
+#################################
+# Custom template tag for tests #
+#################################
+
+register = template.Library()
+
+class EchoNode(template.Node):
+ def __init__(self, contents):
+ self.contents = contents
+
+ def render(self, context):
+ return " ".join(self.contents)
+
+def do_echo(parser, token):
+ return EchoNode(token.contents.split()[1:])
+
+register.tag("echo", do_echo)
+
+template.libraries['django.templatetags.testtags'] = register
+
+#####################################
+# Helper objects for template tests #
+#####################################
+
+class SomeException(Exception):
+ silent_variable_failure = True
+
+class SomeOtherException(Exception):
+ pass
+
+class SomeClass:
+ def __init__(self):
+ self.otherclass = OtherClass()
+
+ def method(self):
+ return "SomeClass.method"
+
+ def method2(self, o):
+ return o
+
+ def method3(self):
+ raise SomeException
+
+ def method4(self):
+ raise SomeOtherException
+
+class OtherClass:
+ def method(self):
+ return "OtherClass.method"
+
+class Templates(unittest.TestCase):
+ def test_templates(self):
+ # NOW and NOW_tz are used by timesince tag tests.
+ NOW = datetime.now()
+ NOW_tz = datetime.now(LocalTimezone(datetime.now()))
+
+ # SYNTAX --
+ # 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class)
+ TEMPLATE_TESTS = {
+
+ ### BASIC SYNTAX ##########################################################
+
+ # Plain text should go through the template parser untouched
+ 'basic-syntax01': ("something cool", {}, "something cool"),
+
+ # Variables should be replaced with their value in the current context
+ 'basic-syntax02': ("{{ headline }}", {'headline':'Success'}, "Success"),
+
+ # More than one replacement variable is allowed in a template
+ 'basic-syntax03': ("{{ first }} --- {{ second }}", {"first" : 1, "second" : 2}, "1 --- 2"),
+
+ # Fail silently when a variable is not found in the current context
+ 'basic-syntax04': ("as{{ missing }}df", {}, ("asdf","asINVALIDdf")),
+
+ # A variable may not contain more than one word
+ 'basic-syntax06': ("{{ multi word variable }}", {}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError for empty variable tags
+ 'basic-syntax07': ("{{ }}", {}, template.TemplateSyntaxError),
+ 'basic-syntax08': ("{{ }}", {}, template.TemplateSyntaxError),
+
+ # Attribute syntax allows a template to call an object's attribute
+ 'basic-syntax09': ("{{ var.method }}", {"var": SomeClass()}, "SomeClass.method"),
+
+ # Multiple levels of attribute access are allowed
+ 'basic-syntax10': ("{{ var.otherclass.method }}", {"var": SomeClass()}, "OtherClass.method"),
+
+ # Fail silently when a variable's attribute isn't found
+ 'basic-syntax11': ("{{ var.blech }}", {"var": SomeClass()}, ("","INVALID")),
+
+ # Raise TemplateSyntaxError when trying to access a variable beginning with an underscore
+ 'basic-syntax12': ("{{ var.__dict__ }}", {"var": SomeClass()}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError when trying to access a variable containing an illegal character
+ 'basic-syntax13': ("{{ va>r }}", {}, template.TemplateSyntaxError),
+ 'basic-syntax14': ("{{ (var.r) }}", {}, template.TemplateSyntaxError),
+ 'basic-syntax15': ("{{ sp%am }}", {}, template.TemplateSyntaxError),
+ 'basic-syntax16': ("{{ eggs! }}", {}, template.TemplateSyntaxError),
+ 'basic-syntax17': ("{{ moo? }}", {}, template.TemplateSyntaxError),
+
+ # Attribute syntax allows a template to call a dictionary key's value
+ 'basic-syntax18': ("{{ foo.bar }}", {"foo" : {"bar" : "baz"}}, "baz"),
+
+ # Fail silently when a variable's dictionary key isn't found
+ 'basic-syntax19': ("{{ foo.spam }}", {"foo" : {"bar" : "baz"}}, ("","INVALID")),
+
+ # Fail silently when accessing a non-simple method
+ 'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, ("","INVALID")),
+
+ # Basic filter usage
+ 'basic-syntax21': ("{{ var|upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"),
+
+ # Chained filters
+ 'basic-syntax22': ("{{ var|upper|lower }}", {"var": "Django is the greatest!"}, "django is the greatest!"),
+
+ # Raise TemplateSyntaxError for space between a variable and filter pipe
+ 'basic-syntax23': ("{{ var |upper }}", {}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError for space after a filter pipe
+ 'basic-syntax24': ("{{ var| upper }}", {}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError for a nonexistent filter
+ 'basic-syntax25': ("{{ var|does_not_exist }}", {}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError when trying to access a filter containing an illegal character
+ 'basic-syntax26': ("{{ var|fil(ter) }}", {}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError for invalid block tags
+ 'basic-syntax27': ("{% nothing_to_see_here %}", {}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError for empty block tags
+ 'basic-syntax28': ("{% %}", {}, template.TemplateSyntaxError),
+
+ # Chained filters, with an argument to the first one
+ 'basic-syntax29': ('{{ var|removetags:"b i"|upper|lower }}', {"var": "<b><i>Yes</i></b>"}, "yes"),
+
+ # Escaped string as argument
+ 'basic-syntax30': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote" hah'),
+
+ # Variable as argument
+ 'basic-syntax31': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'),
+
+ # Default argument testing
+ 'basic-syntax32': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'),
+
+ # Fail silently for methods that raise an exception with a "silent_variable_failure" attribute
+ 'basic-syntax33': (r'1{{ var.method3 }}2', {"var": SomeClass()}, ("12", "1INVALID2")),
+
+ # In methods that raise an exception without a "silent_variable_attribute" set to True,
+ # the exception propogates
+ 'basic-syntax34': (r'1{{ var.method4 }}2', {"var": SomeClass()}, SomeOtherException),
+
+ # Escaped backslash in argument
+ 'basic-syntax35': (r'{{ var|default_if_none:"foo\bar" }}', {"var": None}, r'foo\bar'),
+
+ # Escaped backslash using known escape char
+ 'basic-syntax35': (r'{{ var|default_if_none:"foo\now" }}', {"var": None}, r'foo\now'),
+
+ # Empty strings can be passed as arguments to filters
+ 'basic-syntax36': (r'{{ var|join:"" }}', {'var': ['a', 'b', 'c']}, 'abc'),
+
+ ### COMMENT SYNTAX ########################################################
+ 'comment-syntax01': ("{# this is hidden #}hello", {}, "hello"),
+ 'comment-syntax02': ("{# this is hidden #}hello{# foo #}", {}, "hello"),
+
+ # Comments can contain invalid stuff.
+ 'comment-syntax03': ("foo{# {% if %} #}", {}, "foo"),
+ 'comment-syntax04': ("foo{# {% endblock %} #}", {}, "foo"),
+ 'comment-syntax05': ("foo{# {% somerandomtag %} #}", {}, "foo"),
+ 'comment-syntax06': ("foo{# {% #}", {}, "foo"),
+ 'comment-syntax07': ("foo{# %} #}", {}, "foo"),
+ 'comment-syntax08': ("foo{# %} #}bar", {}, "foobar"),
+ 'comment-syntax09': ("foo{# {{ #}", {}, "foo"),
+ 'comment-syntax10': ("foo{# }} #}", {}, "foo"),
+ 'comment-syntax11': ("foo{# { #}", {}, "foo"),
+ 'comment-syntax12': ("foo{# } #}", {}, "foo"),
+
+ ### COMMENT TAG ###########################################################
+ 'comment-tag01': ("{% comment %}this is hidden{% endcomment %}hello", {}, "hello"),
+ 'comment-tag02': ("{% comment %}this is hidden{% endcomment %}hello{% comment %}foo{% endcomment %}", {}, "hello"),
+
+ # Comment tag can contain invalid stuff.
+ 'comment-tag03': ("foo{% comment %} {% if %} {% endcomment %}", {}, "foo"),
+ 'comment-tag04': ("foo{% comment %} {% endblock %} {% endcomment %}", {}, "foo"),
+ 'comment-tag05': ("foo{% comment %} {% somerandomtag %} {% endcomment %}", {}, "foo"),
+
+ ### CYCLE TAG #############################################################
+ 'cycle01': ('{% cycle a %}', {}, template.TemplateSyntaxError),
+ 'cycle02': ('{% cycle a,b,c as abc %}{% cycle abc %}', {}, 'ab'),
+ 'cycle03': ('{% cycle a,b,c as abc %}{% cycle abc %}{% cycle abc %}', {}, 'abc'),
+ 'cycle04': ('{% cycle a,b,c as abc %}{% cycle abc %}{% cycle abc %}{% cycle abc %}', {}, 'abca'),
+ 'cycle05': ('{% cycle %}', {}, template.TemplateSyntaxError),
+ 'cycle06': ('{% cycle a %}', {}, template.TemplateSyntaxError),
+ 'cycle07': ('{% cycle a,b,c as foo %}{% cycle bar %}', {}, template.TemplateSyntaxError),
+ 'cycle08': ('{% cycle a,b,c as foo %}{% cycle foo %}{{ foo }}{{ foo }}{% cycle foo %}{{ foo }}', {}, 'abbbcc'),
+
+ ### EXCEPTIONS ############################################################
+
+ # Raise exception for invalid template name
+ 'exception01': ("{% extends 'nonexistent' %}", {}, template.TemplateSyntaxError),
+
+ # Raise exception for invalid template name (in variable)
+ 'exception02': ("{% extends nonexistent %}", {}, template.TemplateSyntaxError),
+
+ # Raise exception for extra {% extends %} tags
+ 'exception03': ("{% extends 'inheritance01' %}{% block first %}2{% endblock %}{% extends 'inheritance16' %}", {}, template.TemplateSyntaxError),
+
+ # Raise exception for custom tags used in child with {% load %} tag in parent, not in child
+ 'exception04': ("{% extends 'inheritance17' %}{% block first %}{% echo 400 %}5678{% endblock %}", {}, template.TemplateSyntaxError),
+
+ ### FILTER TAG ############################################################
+ 'filter01': ('{% filter upper %}{% endfilter %}', {}, ''),
+ 'filter02': ('{% filter upper %}django{% endfilter %}', {}, 'DJANGO'),
+ 'filter03': ('{% filter upper|lower %}django{% endfilter %}', {}, 'django'),
+
+ ### FIRSTOF TAG ###########################################################
+ 'firstof01': ('{% firstof a b c %}', {'a':0,'b':0,'c':0}, ''),
+ 'firstof02': ('{% firstof a b c %}', {'a':1,'b':0,'c':0}, '1'),
+ 'firstof03': ('{% firstof a b c %}', {'a':0,'b':2,'c':0}, '2'),
+ 'firstof04': ('{% firstof a b c %}', {'a':0,'b':0,'c':3}, '3'),
+ 'firstof05': ('{% firstof a b c %}', {'a':1,'b':2,'c':3}, '1'),
+ 'firstof06': ('{% firstof %}', {}, template.TemplateSyntaxError),
+
+ ### FOR TAG ###############################################################
+ 'for-tag01': ("{% for val in values %}{{ val }}{% endfor %}", {"values": [1, 2, 3]}, "123"),
+ 'for-tag02': ("{% for val in values reversed %}{{ val }}{% endfor %}", {"values": [1, 2, 3]}, "321"),
+ 'for-tag-vars01': ("{% for val in values %}{{ forloop.counter }}{% endfor %}", {"values": [6, 6, 6]}, "123"),
+ 'for-tag-vars02': ("{% for val in values %}{{ forloop.counter0 }}{% endfor %}", {"values": [6, 6, 6]}, "012"),
+ 'for-tag-vars03': ("{% for val in values %}{{ forloop.revcounter }}{% endfor %}", {"values": [6, 6, 6]}, "321"),
+ 'for-tag-vars04': ("{% for val in values %}{{ forloop.revcounter0 }}{% endfor %}", {"values": [6, 6, 6]}, "210"),
+
+ ### IF TAG ################################################################
+ 'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),
+ 'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"),
+ 'if-tag03': ("{% if foo %}yes{% else %}no{% endif %}", {}, "no"),
+
+ # AND
+ 'if-tag-and01': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
+ 'if-tag-and02': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
+ 'if-tag-and03': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
+ 'if-tag-and04': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
+ 'if-tag-and05': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': False}, 'no'),
+ 'if-tag-and06': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'bar': False}, 'no'),
+ 'if-tag-and07': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True}, 'no'),
+ 'if-tag-and08': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'bar': True}, 'no'),
+
+ # OR
+ 'if-tag-or01': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
+ 'if-tag-or02': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
+ 'if-tag-or03': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
+ 'if-tag-or04': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
+ 'if-tag-or05': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': False}, 'no'),
+ 'if-tag-or06': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'bar': False}, 'no'),
+ 'if-tag-or07': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True}, 'yes'),
+ 'if-tag-or08': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'bar': True}, 'yes'),
+
+ # TODO: multiple ORs
+
+ # NOT
+ 'if-tag-not01': ("{% if not foo %}no{% else %}yes{% endif %}", {'foo': True}, 'yes'),
+ 'if-tag-not02': ("{% if not %}yes{% else %}no{% endif %}", {'foo': True}, 'no'),
+ 'if-tag-not03': ("{% if not %}yes{% else %}no{% endif %}", {'not': True}, 'yes'),
+ 'if-tag-not04': ("{% if not not %}no{% else %}yes{% endif %}", {'not': True}, 'yes'),
+ 'if-tag-not05': ("{% if not not %}no{% else %}yes{% endif %}", {}, 'no'),
+
+ 'if-tag-not06': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {}, 'no'),
+ 'if-tag-not07': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
+ 'if-tag-not08': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
+ 'if-tag-not09': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
+ 'if-tag-not10': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
+
+ 'if-tag-not11': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {}, 'no'),
+ 'if-tag-not12': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
+ 'if-tag-not13': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
+ 'if-tag-not14': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
+ 'if-tag-not15': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
+
+ 'if-tag-not16': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {}, 'yes'),
+ 'if-tag-not17': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
+ 'if-tag-not18': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
+ 'if-tag-not19': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
+ 'if-tag-not20': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
+
+ 'if-tag-not21': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {}, 'yes'),
+ 'if-tag-not22': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
+ 'if-tag-not23': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
+ 'if-tag-not24': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
+ 'if-tag-not25': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
+
+ 'if-tag-not26': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {}, 'yes'),
+ 'if-tag-not27': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
+ 'if-tag-not28': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
+ 'if-tag-not29': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
+ 'if-tag-not30': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
+
+ 'if-tag-not31': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {}, 'yes'),
+ 'if-tag-not32': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
+ 'if-tag-not33': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
+ 'if-tag-not34': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
+ 'if-tag-not35': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
+
+ # AND and OR raises a TemplateSyntaxError
+ 'if-tag-error01': ("{% if foo or bar and baz %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, template.TemplateSyntaxError),
+ 'if-tag-error02': ("{% if foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
+ 'if-tag-error03': ("{% if foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
+ 'if-tag-error04': ("{% if not foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
+ 'if-tag-error05': ("{% if not foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
+
+ ### IFCHANGED TAG #########################################################
+ 'ifchanged01': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,2,3) }, '123'),
+ 'ifchanged02': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,3) }, '13'),
+ 'ifchanged03': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,1) }, '1'),
+ 'ifchanged04': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 2, 3), 'numx': (2, 2, 2)}, '122232'),
+ 'ifchanged05': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (1, 2, 3)}, '1123123123'),
+ 'ifchanged06': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (2, 2, 2)}, '1222'),
+ 'ifchanged07': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% for y in numy %}{% ifchanged %}{{ y }}{% endifchanged %}{% endfor %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (2, 2, 2), 'numy': (3, 3, 3)}, '1233323332333'),
+
+ ### IFEQUAL TAG ###########################################################
+ 'ifequal01': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 2}, ""),
+ 'ifequal02': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 1}, "yes"),
+ 'ifequal03': ("{% ifequal a b %}yes{% else %}no{% endifequal %}", {"a": 1, "b": 2}, "no"),
+ 'ifequal04': ("{% ifequal a b %}yes{% else %}no{% endifequal %}", {"a": 1, "b": 1}, "yes"),
+ 'ifequal05': ("{% ifequal a 'test' %}yes{% else %}no{% endifequal %}", {"a": "test"}, "yes"),
+ 'ifequal06': ("{% ifequal a 'test' %}yes{% else %}no{% endifequal %}", {"a": "no"}, "no"),
+ 'ifequal07': ('{% ifequal a "test" %}yes{% else %}no{% endifequal %}', {"a": "test"}, "yes"),
+ 'ifequal08': ('{% ifequal a "test" %}yes{% else %}no{% endifequal %}', {"a": "no"}, "no"),
+ 'ifequal09': ('{% ifequal a "test" %}yes{% else %}no{% endifequal %}', {}, "no"),
+ 'ifequal10': ('{% ifequal a b %}yes{% else %}no{% endifequal %}', {}, "yes"),
+
+ # SMART SPLITTING
+ 'ifequal-split01': ('{% ifequal a "test man" %}yes{% else %}no{% endifequal %}', {}, "no"),
+ 'ifequal-split02': ('{% ifequal a "test man" %}yes{% else %}no{% endifequal %}', {'a': 'foo'}, "no"),
+ 'ifequal-split03': ('{% ifequal a "test man" %}yes{% else %}no{% endifequal %}', {'a': 'test man'}, "yes"),
+ 'ifequal-split04': ("{% ifequal a 'test man' %}yes{% else %}no{% endifequal %}", {'a': 'test man'}, "yes"),
+ 'ifequal-split05': ("{% ifequal a 'i \"love\" you' %}yes{% else %}no{% endifequal %}", {'a': ''}, "no"),
+ 'ifequal-split06': ("{% ifequal a 'i \"love\" you' %}yes{% else %}no{% endifequal %}", {'a': 'i "love" you'}, "yes"),
+ 'ifequal-split07': ("{% ifequal a 'i \"love\" you' %}yes{% else %}no{% endifequal %}", {'a': 'i love you'}, "no"),
+ 'ifequal-split08': (r"{% ifequal a 'I\'m happy' %}yes{% else %}no{% endifequal %}", {'a': "I'm happy"}, "yes"),
+ 'ifequal-split09': (r"{% ifequal a 'slash\man' %}yes{% else %}no{% endifequal %}", {'a': r"slash\man"}, "yes"),
+ 'ifequal-split10': (r"{% ifequal a 'slash\man' %}yes{% else %}no{% endifequal %}", {'a': r"slashman"}, "no"),
+
+ ### IFNOTEQUAL TAG ########################################################
+ 'ifnotequal01': ("{% ifnotequal a b %}yes{% endifnotequal %}", {"a": 1, "b": 2}, "yes"),
+ 'ifnotequal02': ("{% ifnotequal a b %}yes{% endifnotequal %}", {"a": 1, "b": 1}, ""),
+ 'ifnotequal03': ("{% ifnotequal a b %}yes{% else %}no{% endifnotequal %}", {"a": 1, "b": 2}, "yes"),
+ 'ifnotequal04': ("{% ifnotequal a b %}yes{% else %}no{% endifnotequal %}", {"a": 1, "b": 1}, "no"),
+
+ ### INCLUDE TAG ###########################################################
+ 'include01': ('{% include "basic-syntax01" %}', {}, "something cool"),
+ 'include02': ('{% include "basic-syntax02" %}', {'headline': 'Included'}, "Included"),
+ 'include03': ('{% include template_name %}', {'template_name': 'basic-syntax02', 'headline': 'Included'}, "Included"),
+ 'include04': ('a{% include "nonexistent" %}b', {}, "ab"),
+
+ ### INHERITANCE ###########################################################
+
+ # Standard template with no inheritance
+ 'inheritance01': ("1{% block first %}_{% endblock %}3{% block second %}_{% endblock %}", {}, '1_3_'),
+
+ # Standard two-level inheritance
+ 'inheritance02': ("{% extends 'inheritance01' %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {}, '1234'),
+
+ # Three-level with no redefinitions on third level
+ 'inheritance03': ("{% extends 'inheritance02' %}", {}, '1234'),
+
+ # Two-level with no redefinitions on second level
+ 'inheritance04': ("{% extends 'inheritance01' %}", {}, '1_3_'),
+
+ # Two-level with double quotes instead of single quotes
+ 'inheritance05': ('{% extends "inheritance02" %}', {}, '1234'),
+
+ # Three-level with variable parent-template name
+ 'inheritance06': ("{% extends foo %}", {'foo': 'inheritance02'}, '1234'),
+
+ # Two-level with one block defined, one block not defined
+ 'inheritance07': ("{% extends 'inheritance01' %}{% block second %}5{% endblock %}", {}, '1_35'),
+
+ # Three-level with one block defined on this level, two blocks defined next level
+ 'inheritance08': ("{% extends 'inheritance02' %}{% block second %}5{% endblock %}", {}, '1235'),
+
+ # Three-level with second and third levels blank
+ 'inheritance09': ("{% extends 'inheritance04' %}", {}, '1_3_'),
+
+ # Three-level with space NOT in a block -- should be ignored
+ 'inheritance10': ("{% extends 'inheritance04' %} ", {}, '1_3_'),
+
+ # Three-level with both blocks defined on this level, but none on second level
+ 'inheritance11': ("{% extends 'inheritance04' %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {}, '1234'),
+
+ # Three-level with this level providing one and second level providing the other
+ 'inheritance12': ("{% extends 'inheritance07' %}{% block first %}2{% endblock %}", {}, '1235'),
+
+ # Three-level with this level overriding second level
+ 'inheritance13': ("{% extends 'inheritance02' %}{% block first %}a{% endblock %}{% block second %}b{% endblock %}", {}, '1a3b'),
+
+ # A block defined only in a child template shouldn't be displayed
+ 'inheritance14': ("{% extends 'inheritance01' %}{% block newblock %}NO DISPLAY{% endblock %}", {}, '1_3_'),
+
+ # A block within another block
+ 'inheritance15': ("{% extends 'inheritance01' %}{% block first %}2{% block inner %}inner{% endblock %}{% endblock %}", {}, '12inner3_'),
+
+ # A block within another block (level 2)
+ 'inheritance16': ("{% extends 'inheritance15' %}{% block inner %}out{% endblock %}", {}, '12out3_'),
+
+ # {% load %} tag (parent -- setup for exception04)
+ 'inheritance17': ("{% load testtags %}{% block first %}1234{% endblock %}", {}, '1234'),
+
+ # {% load %} tag (standard usage, without inheritance)
+ 'inheritance18': ("{% load testtags %}{% echo this that theother %}5678", {}, 'this that theother5678'),
+
+ # {% load %} tag (within a child template)
+ 'inheritance19': ("{% extends 'inheritance01' %}{% block first %}{% load testtags %}{% echo 400 %}5678{% endblock %}", {}, '140056783_'),
+
+ # Two-level inheritance with {{ block.super }}
+ 'inheritance20': ("{% extends 'inheritance01' %}{% block first %}{{ block.super }}a{% endblock %}", {}, '1_a3_'),
+
+ # Three-level inheritance with {{ block.super }} from parent
+ 'inheritance21': ("{% extends 'inheritance02' %}{% block first %}{{ block.super }}a{% endblock %}", {}, '12a34'),
+
+ # Three-level inheritance with {{ block.super }} from grandparent
+ 'inheritance22': ("{% extends 'inheritance04' %}{% block first %}{{ block.super }}a{% endblock %}", {}, '1_a3_'),
+
+ # Three-level inheritance with {{ block.super }} from parent and grandparent
+ 'inheritance23': ("{% extends 'inheritance20' %}{% block first %}{{ block.super }}b{% endblock %}", {}, '1_ab3_'),
+
+ # Inheritance from local context without use of template loader
+ 'inheritance24': ("{% extends context_template %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {'context_template': template.Template("1{% block first %}_{% endblock %}3{% block second %}_{% endblock %}")}, '1234'),
+
+ # Inheritance from local context with variable parent template
+ 'inheritance25': ("{% extends context_template.1 %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {'context_template': [template.Template("Wrong"), template.Template("1{% block first %}_{% endblock %}3{% block second %}_{% endblock %}")]}, '1234'),
+
+ ### I18N ##################################################################
+
+ # {% spaceless %} tag
+ 'spaceless01': ("{% spaceless %} <b> <i> text </i> </b> {% endspaceless %}", {}, "<b> <i> text </i> </b>"),
+ 'spaceless02': ("{% spaceless %} <b> \n <i> text </i> \n </b> {% endspaceless %}", {}, "<b> <i> text </i> </b>"),
+ 'spaceless03': ("{% spaceless %}<b><i>text</i></b>{% endspaceless %}", {}, "<b><i>text</i></b>"),
+
+ # simple translation of a string delimited by '
+ 'i18n01': ("{% load i18n %}{% trans 'xxxyyyxxx' %}", {}, "xxxyyyxxx"),
+
+ # simple translation of a string delimited by "
+ 'i18n02': ('{% load i18n %}{% trans "xxxyyyxxx" %}', {}, "xxxyyyxxx"),
+
+ # simple translation of a variable
+ 'i18n03': ('{% load i18n %}{% blocktrans %}{{ anton }}{% endblocktrans %}', {'anton': 'xxxyyyxxx'}, "xxxyyyxxx"),
+
+ # simple translation of a variable and filter
+ 'i18n04': ('{% load i18n %}{% blocktrans with anton|lower as berta %}{{ berta }}{% endblocktrans %}', {'anton': 'XXXYYYXXX'}, "xxxyyyxxx"),
+
+ # simple translation of a string with interpolation
+ 'i18n05': ('{% load i18n %}{% blocktrans %}xxx{{ anton }}xxx{% endblocktrans %}', {'anton': 'yyy'}, "xxxyyyxxx"),
+
+ # simple translation of a string to german
+ 'i18n06': ('{% load i18n %}{% trans "Page not found" %}', {'LANGUAGE_CODE': 'de'}, "Seite nicht gefunden"),
+
+ # translation of singular form
+ 'i18n07': ('{% load i18n %}{% blocktrans count number as counter %}singular{% plural %}plural{% endblocktrans %}', {'number': 1}, "singular"),
+
+ # translation of plural form
+ 'i18n08': ('{% load i18n %}{% blocktrans count number as counter %}singular{% plural %}plural{% endblocktrans %}', {'number': 2}, "plural"),
+
+ # simple non-translation (only marking) of a string to german
+ 'i18n09': ('{% load i18n %}{% trans "Page not found" noop %}', {'LANGUAGE_CODE': 'de'}, "Page not found"),
+
+ # translation of a variable with a translated filter
+ 'i18n10': ('{{ bool|yesno:_("ja,nein") }}', {'bool': True}, 'ja'),
+
+ # translation of a variable with a non-translated filter
+ 'i18n11': ('{{ bool|yesno:"ja,nein" }}', {'bool': True}, 'ja'),
+
+ # usage of the get_available_languages tag
+ 'i18n12': ('{% load i18n %}{% get_available_languages as langs %}{% for lang in langs %}{% ifequal lang.0 "de" %}{{ lang.0 }}{% endifequal %}{% endfor %}', {}, 'de'),
+
+ # translation of a constant string
+ 'i18n13': ('{{ _("Page not found") }}', {'LANGUAGE_CODE': 'de'}, 'Seite nicht gefunden'),
+
+ ### HANDLING OF TEMPLATE_TAG_IF_INVALID ###################################
+
+ 'invalidstr01': ('{{ var|default:"Foo" }}', {}, ('Foo','INVALID')),
+ 'invalidstr02': ('{{ var|default_if_none:"Foo" }}', {}, ('','INVALID')),
+ 'invalidstr03': ('{% for v in var %}({{ v }}){% endfor %}', {}, ''),
+ 'invalidstr04': ('{% if var %}Yes{% else %}No{% endif %}', {}, 'No'),
+ 'invalidstr04': ('{% if var|default:"Foo" %}Yes{% else %}No{% endif %}', {}, 'Yes'),
+
+ ### MULTILINE #############################################################
+
+ 'multiline01': ("""
+ Hello,
+ boys.
+ How
+ are
+ you
+ gentlemen.
+ """,
+ {},
+ """
+ Hello,
+ boys.
+ How
+ are
+ you
+ gentlemen.
+ """),
+
+ ### REGROUP TAG ###########################################################
+ 'regroup01': ('{% regroup data by bar as grouped %}' + \
+ '{% for group in grouped %}' + \
+ '{{ group.grouper }}:' + \
+ '{% for item in group.list %}' + \
+ '{{ item.foo }}' + \
+ '{% endfor %},' + \
+ '{% endfor %}',
+ {'data': [ {'foo':'c', 'bar':1},
+ {'foo':'d', 'bar':1},
+ {'foo':'a', 'bar':2},
+ {'foo':'b', 'bar':2},
+ {'foo':'x', 'bar':3} ]},
+ '1:cd,2:ab,3:x,'),
+
+ # Test for silent failure when target variable isn't found
+ 'regroup02': ('{% regroup data by bar as grouped %}' + \
+ '{% for group in grouped %}' + \
+ '{{ group.grouper }}:' + \
+ '{% for item in group.list %}' + \
+ '{{ item.foo }}' + \
+ '{% endfor %},' + \
+ '{% endfor %}',
+ {}, ''),
+
+ ### TEMPLATETAG TAG #######################################################
+ 'templatetag01': ('{% templatetag openblock %}', {}, '{%'),
+ 'templatetag02': ('{% templatetag closeblock %}', {}, '%}'),
+ 'templatetag03': ('{% templatetag openvariable %}', {}, '{{'),
+ 'templatetag04': ('{% templatetag closevariable %}', {}, '}}'),
+ 'templatetag05': ('{% templatetag %}', {}, template.TemplateSyntaxError),
+ 'templatetag06': ('{% templatetag foo %}', {}, template.TemplateSyntaxError),
+ 'templatetag07': ('{% templatetag openbrace %}', {}, '{'),
+ 'templatetag08': ('{% templatetag closebrace %}', {}, '}'),
+ 'templatetag09': ('{% templatetag openbrace %}{% templatetag openbrace %}', {}, '{{'),
+ 'templatetag10': ('{% templatetag closebrace %}{% templatetag closebrace %}', {}, '}}'),
+
+ ### WIDTHRATIO TAG ########################################################
+ 'widthratio01': ('{% widthratio a b 0 %}', {'a':50,'b':100}, '0'),
+ 'widthratio02': ('{% widthratio a b 100 %}', {'a':0,'b':0}, ''),
+ 'widthratio03': ('{% widthratio a b 100 %}', {'a':0,'b':100}, '0'),
+ 'widthratio04': ('{% widthratio a b 100 %}', {'a':50,'b':100}, '50'),
+ 'widthratio05': ('{% widthratio a b 100 %}', {'a':100,'b':100}, '100'),
+
+ # 62.5 should round to 63
+ 'widthratio06': ('{% widthratio a b 100 %}', {'a':50,'b':80}, '63'),
+
+ # 71.4 should round to 71
+ 'widthratio07': ('{% widthratio a b 100 %}', {'a':50,'b':70}, '71'),
+
+ # Raise exception if we don't have 3 args, last one an integer
+ 'widthratio08': ('{% widthratio %}', {}, template.TemplateSyntaxError),
+ 'widthratio09': ('{% widthratio a b %}', {'a':50,'b':100}, template.TemplateSyntaxError),
+ 'widthratio10': ('{% widthratio a b 100.0 %}', {'a':50,'b':100}, template.TemplateSyntaxError),
+
+ ### NOW TAG ########################################################
+ # Simple case
+ 'now01' : ('{% now "j n Y"%}', {}, str(datetime.now().day) + ' ' + str(datetime.now().month) + ' ' + str(datetime.now().year)),
+
+ # Check parsing of escaped and special characters
+ 'now02' : ('{% now "j "n" Y"%}', {}, template.TemplateSyntaxError),
+ # 'now03' : ('{% now "j \"n\" Y"%}', {}, str(datetime.now().day) + '"' + str(datetime.now().month) + '"' + str(datetime.now().year)),
+ # 'now04' : ('{% now "j \nn\n Y"%}', {}, str(datetime.now().day) + '\n' + str(datetime.now().month) + '\n' + str(datetime.now().year))
+
+ ### TIMESINCE TAG ##################################################
+ # Default compare with datetime.now()
+ 'timesince01' : ('{{ a|timesince }}', {'a':datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1 minute'),
+ 'timesince02' : ('{{ a|timesince }}', {'a':(datetime.now() - timedelta(days=1, minutes = 1))}, '1 day'),
+ 'timesince03' : ('{{ a|timesince }}', {'a':(datetime.now() -
+ timedelta(hours=1, minutes=25, seconds = 10))}, '1 hour, 25 minutes'),
+
+ # Compare to a given parameter
+ 'timesince04' : ('{{ a|timesince:b }}', {'a':NOW + timedelta(days=2), 'b':NOW + timedelta(days=1)}, '1 day'),
+ 'timesince05' : ('{{ a|timesince:b }}', {'a':NOW + timedelta(days=2, minutes=1), 'b':NOW + timedelta(days=2)}, '1 minute'),
+
+ # Check that timezone is respected
+ 'timesince06' : ('{{ a|timesince:b }}', {'a':NOW_tz + timedelta(hours=8), 'b':NOW_tz}, '8 hours'),
+
+ ### TIMEUNTIL TAG ##################################################
+ # Default compare with datetime.now()
+ 'timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'),
+ 'timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1 day'),
+ 'timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10, seconds = 10))}, '8 hours, 10 minutes'),
+
+ # Compare to a given parameter
+ 'timeuntil04' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=1), 'b':NOW - timedelta(days=2)}, '1 day'),
+ 'timeuntil05' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=2), 'b':NOW - timedelta(days=2, minutes=1)}, '1 minute'),
+ }
+
+ # Register our custom template loader.
+ def test_template_loader(template_name, template_dirs=None):
+ "A custom template loader that loads the unit-test templates."
+ try:
+ return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name)
+ except KeyError:
+ raise template.TemplateDoesNotExist, template_name
+
+ old_template_loaders = loader.template_source_loaders
+ loader.template_source_loaders = [test_template_loader]
+
+ failures = []
+ tests = TEMPLATE_TESTS.items()
+ tests.sort()
+
+ # Turn TEMPLATE_DEBUG off, because tests assume that.
+ old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
+
+ # Set TEMPLATE_STRING_IF_INVALID to a known string
+ old_invalid = settings.TEMPLATE_STRING_IF_INVALID
+
+ for name, vals in tests:
+ install()
+
+ if isinstance(vals[2], tuple):
+ normal_string_result = vals[2][0]
+ invalid_string_result = vals[2][1]
+ else:
+ normal_string_result = vals[2]
+ invalid_string_result = vals[2]
+
+ if 'LANGUAGE_CODE' in vals[1]:
+ activate(vals[1]['LANGUAGE_CODE'])
+ else:
+ activate('en-us')
+
+ for invalid_str, result in [('', normal_string_result),
+ ('INVALID', invalid_string_result)]:
+ settings.TEMPLATE_STRING_IF_INVALID = invalid_str
+ try:
+ output = loader.get_template(name).render(template.Context(vals[1]))
+ except Exception, e:
+ if e.__class__ != result:
+ failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e))
+ continue
+ if output != result:
+ failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output))
+
+ if 'LANGUAGE_CODE' in vals[1]:
+ deactivate()
+
+ loader.template_source_loaders = old_template_loaders
+ deactivate()
+ settings.TEMPLATE_DEBUG = old_td
+ settings.TEMPLATE_STRING_IF_INVALID = old_invalid
+
+ self.assertEqual(failures, [], '\n'.join(failures))
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/regressiontests/urlpatterns_reverse/__init__.py b/tests/regressiontests/urlpatterns_reverse/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/urlpatterns_reverse/__init__.py
diff --git a/tests/regressiontests/urlpatterns_reverse/models.py b/tests/regressiontests/urlpatterns_reverse/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/urlpatterns_reverse/models.py
diff --git a/tests/regressiontests/urlpatterns_reverse/tests.py b/tests/regressiontests/urlpatterns_reverse/tests.py
new file mode 100644
index 0000000000..8f571ac66c
--- /dev/null
+++ b/tests/regressiontests/urlpatterns_reverse/tests.py
@@ -0,0 +1,39 @@
+"Unit tests for reverse URL lookup"
+
+from django.core.urlresolvers import reverse_helper, NoReverseMatch
+import re, unittest
+
+test_data = (
+ ('^places/(\d+)/$', 'places/3/', [3], {}),
+ ('^places/(\d+)/$', 'places/3/', ['3'], {}),
+ ('^places/(\d+)/$', NoReverseMatch, ['a'], {}),
+ ('^places/(\d+)/$', NoReverseMatch, [], {}),
+ ('^places/(?P<id>\d+)/$', 'places/3/', [], {'id': 3}),
+ ('^people/(?P<name>\w+)/$', 'people/adrian/', ['adrian'], {}),
+ ('^people/(?P<name>\w+)/$', 'people/adrian/', [], {'name': 'adrian'}),
+ ('^people/(?P<name>\w+)/$', NoReverseMatch, ['name with spaces'], {}),
+ ('^people/(?P<name>\w+)/$', NoReverseMatch, [], {'name': 'name with spaces'}),
+ ('^people/(?P<name>\w+)/$', NoReverseMatch, [], {}),
+ ('^hardcoded/$', 'hardcoded/', [], {}),
+ ('^hardcoded/$', 'hardcoded/', ['any arg'], {}),
+ ('^hardcoded/$', 'hardcoded/', [], {'kwarg': 'foo'}),
+ ('^people/(?P<state>\w\w)/(?P<name>\w+)/$', 'people/il/adrian/', [], {'state': 'il', 'name': 'adrian'}),
+ ('^people/(?P<state>\w\w)/(?P<name>\d)/$', NoReverseMatch, [], {'state': 'il', 'name': 'adrian'}),
+ ('^people/(?P<state>\w\w)/(?P<name>\w+)/$', NoReverseMatch, [], {'state': 'il'}),
+ ('^people/(?P<state>\w\w)/(?P<name>\w+)/$', NoReverseMatch, [], {'name': 'adrian'}),
+ ('^people/(?P<state>\w\w)/(\w+)/$', NoReverseMatch, ['il'], {'name': 'adrian'}),
+ ('^people/(?P<state>\w\w)/(\w+)/$', 'people/il/adrian/', ['adrian'], {'state': 'il'}),
+)
+
+class URLPatternReverse(unittest.TestCase):
+ def test_urlpattern_reverse(self):
+ for regex, expected, args, kwargs in test_data:
+ try:
+ got = reverse_helper(re.compile(regex), *args, **kwargs)
+ except NoReverseMatch, e:
+ self.assertEqual(expected, NoReverseMatch)
+ else:
+ self.assertEquals(got, expected)
+
+if __name__ == "__main__":
+ run_tests(1)
diff --git a/tests/runtests.py b/tests/runtests.py
index b98a739249..359fca2bf5 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -1,23 +1,12 @@
#!/usr/bin/env python
-import os, re, sys, time, traceback
-
-# doctest is included in the same package as this module, because this testing
-# framework uses features only available in the Python 2.4 version of doctest,
-# and Django aims to work with Python 2.3+.
-import doctest
+import os, sys, traceback
+import unittest
MODEL_TESTS_DIR_NAME = 'modeltests'
-OTHER_TESTS_DIR = "othertests"
REGRESSION_TESTS_DIR_NAME = 'regressiontests'
TEST_DATABASE_NAME = 'django_test_db'
-
-error_list = []
-def log_error(model_name, title, description):
- error_list.append({
- 'title': "%r module: %s" % (model_name, title),
- 'description': description,
- })
+TEST_TEMPLATE_DIR = 'templates'
MODEL_TEST_DIR = os.path.join(os.path.dirname(__file__), MODEL_TESTS_DIR_NAME)
REGRESSION_TEST_DIR = os.path.join(os.path.dirname(__file__), REGRESSION_TESTS_DIR_NAME)
@@ -37,258 +26,113 @@ def get_test_models():
models = []
for loc, dirpath in (MODEL_TESTS_DIR_NAME, MODEL_TEST_DIR), (REGRESSION_TESTS_DIR_NAME, REGRESSION_TEST_DIR):
for f in os.listdir(dirpath):
- if f.startswith('__init__') or f.startswith('.') or f.startswith('sql'):
+ if f.startswith('__init__') or f.startswith('.') or f.startswith('sql') or f.startswith('invalid'):
continue
models.append((loc, f))
return models
-class DjangoDoctestRunner(doctest.DocTestRunner):
- def __init__(self, verbosity_level, *args, **kwargs):
- self.verbosity_level = verbosity_level
- doctest.DocTestRunner.__init__(self, *args, **kwargs)
- self._checker = DjangoDoctestOutputChecker()
- self.optionflags = doctest.ELLIPSIS
-
- def report_start(self, out, test, example):
- if self.verbosity_level > 1:
- out(" >>> %s\n" % example.source.strip())
-
- def report_failure(self, out, test, example, got):
- log_error(test.name, "API test failed",
- "Code: %r\nLine: %s\nExpected: %r\nGot: %r" % (example.source.strip(), example.lineno, example.want, got))
-
- def report_unexpected_exception(self, out, test, example, exc_info):
- from django.db import transaction
- tb = ''.join(traceback.format_exception(*exc_info)[1:])
- log_error(test.name, "API test raised an exception",
- "Code: %r\nLine: %s\nException: %s" % (example.source.strip(), example.lineno, tb))
- # Rollback, in case of database errors. Otherwise they'd have
- # side effects on other tests.
- transaction.rollback_unless_managed()
-
-normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
-
-class DjangoDoctestOutputChecker(doctest.OutputChecker):
- def check_output(self, want, got, optionflags):
- ok = doctest.OutputChecker.check_output(self, want, got, optionflags)
-
- # Doctest does an exact string comparison of output, which means long
- # integers aren't equal to normal integers ("22L" vs. "22"). The
- # following code normalizes long integers so that they equal normal
- # integers.
- if not ok:
- return normalize_long_ints(want) == normalize_long_ints(got)
- return ok
-
-class TestRunner:
- def __init__(self, verbosity_level=0, which_tests=None):
- self.verbosity_level = verbosity_level
- self.which_tests = which_tests
-
- def output(self, required_level, message):
- if self.verbosity_level > required_level - 1:
- print message
-
- def run_tests(self):
- from django.conf import settings
-
- # An empty access of the settings to force the default options to be
- # installed prior to assigning to them.
- settings.INSTALLED_APPS
-
- # Manually set INSTALLED_APPS to point to the test models.
- settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS + ['.'.join(a) for a in get_test_models()]
-
- # Manually set DEBUG and USE_I18N.
- settings.DEBUG = False
- settings.USE_I18N = True
-
- from django.db import connection
+def get_invalid_models():
+ models = []
+ for loc, dirpath in (MODEL_TESTS_DIR_NAME, MODEL_TEST_DIR), (REGRESSION_TESTS_DIR_NAME, REGRESSION_TEST_DIR):
+ for f in os.listdir(dirpath):
+ if f.startswith('__init__') or f.startswith('.') or f.startswith('sql'):
+ continue
+ if f.startswith('invalid'):
+ models.append((loc, f))
+ return models
+
+class InvalidModelTestCase(unittest.TestCase):
+ def __init__(self, model_label):
+ unittest.TestCase.__init__(self)
+ self.model_label = model_label
+
+ def runTest(self):
from django.core import management
- import django.db.models
-
- # Determine which models we're going to test.
- test_models = get_test_models()
- if 'othertests' in self.which_tests:
- self.which_tests.remove('othertests')
- run_othertests = True
- if not self.which_tests:
- test_models = []
- else:
- run_othertests = not self.which_tests
-
- if self.which_tests:
- # Only run the specified tests.
- bad_models = [m for m in self.which_tests if (MODEL_TESTS_DIR_NAME, m) not in test_models and (REGRESSION_TESTS_DIR_NAME, m) not in test_models]
- if bad_models:
- sys.stderr.write("Models not found: %s\n" % bad_models)
- sys.exit(1)
- else:
- all_tests = []
- for test in self.which_tests:
- for loc in MODEL_TESTS_DIR_NAME, REGRESSION_TESTS_DIR_NAME:
- if (loc, test) in test_models:
- all_tests.append((loc, test))
- test_models = all_tests
-
- self.output(0, "Running tests with database %r" % settings.DATABASE_ENGINE)
-
- # If we're using SQLite, it's more convenient to test against an
- # in-memory database.
- if settings.DATABASE_ENGINE == "sqlite3":
- global TEST_DATABASE_NAME
- TEST_DATABASE_NAME = ":memory:"
- else:
- # Create the test database and connect to it. We need to autocommit
- # if the database supports it because PostgreSQL doesn't allow
- # CREATE/DROP DATABASE statements within transactions.
- cursor = connection.cursor()
- self._set_autocommit(connection)
- self.output(1, "Creating test database")
- try:
- cursor.execute("CREATE DATABASE %s" % TEST_DATABASE_NAME)
- except Exception, e:
- sys.stderr.write("Got an error creating the test database: %s\n" % e)
- confirm = raw_input("It appears the test database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_NAME)
- if confirm == 'yes':
- cursor.execute("DROP DATABASE %s" % TEST_DATABASE_NAME)
- cursor.execute("CREATE DATABASE %s" % TEST_DATABASE_NAME)
- else:
- print "Tests cancelled."
- return
- connection.close()
- old_database_name = settings.DATABASE_NAME
- settings.DATABASE_NAME = TEST_DATABASE_NAME
-
- # Initialize the test database.
- cursor = connection.cursor()
-
from django.db.models.loading import load_app
- # Install the core always installed apps
- for app in ALWAYS_INSTALLED_APPS:
- self.output(1, "Installing contrib app %s" % app)
- mod = load_app(app)
- management.install(mod)
-
- # Run the tests for each test model.
- self.output(1, "Running app tests")
- for model_dir, model_name in test_models:
- self.output(1, "%s model: Importing" % model_name)
- try:
- mod = load_app(model_dir + '.' + model_name)
- except Exception, e:
- log_error(model_name, "Error while importing", ''.join(traceback.format_exception(*sys.exc_info())[1:]))
- continue
-
- if not getattr(mod, 'error_log', None):
- # Model is not marked as an invalid model
- self.output(1, "%s.%s model: Installing" % (model_dir, model_name))
- management.install(mod)
-
- # Run the API tests.
- p = doctest.DocTestParser()
- test_namespace = dict([(m._meta.object_name, m) \
- for m in django.db.models.get_models(mod)])
- dtest = p.get_doctest(mod.API_TESTS, test_namespace, model_name, None, None)
- # Manually set verbose=False, because "-v" command-line parameter
- # has side effects on doctest TestRunner class.
- runner = DjangoDoctestRunner(verbosity_level=verbosity_level, verbose=False)
- self.output(1, "%s.%s model: Running tests" % (model_dir, model_name))
- runner.run(dtest, clear_globs=True, out=sys.stdout.write)
- else:
- # Check that model known to be invalid is invalid for the right reasons.
- self.output(1, "%s.%s model: Validating" % (model_dir, model_name))
-
- from cStringIO import StringIO
- s = StringIO()
- count = management.get_validation_errors(s, mod)
- s.seek(0)
- error_log = s.read()
- actual = error_log.split('\n')
- expected = mod.error_log.split('\n')
-
- unexpected = [err for err in actual if err not in expected]
- missing = [err for err in expected if err not in actual]
+ from cStringIO import StringIO
- if unexpected or missing:
- unexpected_log = '\n'.join(unexpected)
- missing_log = '\n'.join(missing)
- log_error(model_name,
- "Validator found %d validation errors, %d expected" % (count, len(expected) - 1),
- "Missing errors:\n%s\n\nUnexpected errors:\n%s" % (missing_log, unexpected_log))
+ try:
+ module = load_app(self.model_label)
+ except Exception, e:
+ self.fail('Unable to load invalid model module')
+
+ s = StringIO()
+ count = management.get_validation_errors(s, module)
+ s.seek(0)
+ error_log = s.read()
+ actual = error_log.split('\n')
+ expected = module.model_errors.split('\n')
- if run_othertests:
- # Run the non-model tests in the other tests dir
- self.output(1, "Running other tests")
- other_tests_dir = os.path.join(os.path.dirname(__file__), OTHER_TESTS_DIR)
- test_modules = [f[:-3] for f in os.listdir(other_tests_dir) if f.endswith('.py') and not f.startswith('__init__')]
- for module in test_modules:
- self.output(1, "%s module: Importing" % module)
- try:
- mod = __import__("othertests." + module, '', '', [''])
- except Exception, e:
- log_error(module, "Error while importing", ''.join(traceback.format_exception(*sys.exc_info())[1:]))
- continue
- if mod.__doc__:
- p = doctest.DocTestParser()
- dtest = p.get_doctest(mod.__doc__, mod.__dict__, module, None, None)
- runner = DjangoDoctestRunner(verbosity_level=verbosity_level, verbose=False)
- self.output(1, "%s module: running tests" % module)
- runner.run(dtest, clear_globs=True, out=sys.stdout.write)
- if hasattr(mod, "run_tests") and callable(mod.run_tests):
- self.output(1, "%s module: running tests" % module)
- try:
- mod.run_tests(verbosity_level)
- except Exception, e:
- log_error(module, "Exception running tests", ''.join(traceback.format_exception(*sys.exc_info())[1:]))
- continue
+ unexpected = [err for err in actual if err not in expected]
+ missing = [err for err in expected if err not in actual]
- # Unless we're using SQLite, remove the test database to clean up after
- # ourselves. Connect to the previous database (not the test database)
- # to do so, because it's not allowed to delete a database while being
- # connected to it.
- if settings.DATABASE_ENGINE != "sqlite3":
- connection.close()
- settings.DATABASE_NAME = old_database_name
- cursor = connection.cursor()
- self.output(1, "Deleting test database")
- self._set_autocommit(connection)
- time.sleep(1) # To avoid "database is being accessed by other users" errors.
- cursor.execute("DROP DATABASE %s" % TEST_DATABASE_NAME)
+ self.assert_(not unexpected, "Unexpected Errors: " + '\n'.join(unexpected))
+ self.assert_(not missing, "Missing Errors: " + '\n'.join(missing))
- # Display output.
- if error_list:
- for d in error_list:
- print
- print d['title']
- print "=" * len(d['title'])
- print d['description']
- print "%s error%s:" % (len(error_list), len(error_list) != 1 and 's' or '')
- else:
- print "All tests passed."
+def django_tests(verbosity, tests_to_run):
+ from django.conf import settings
+ from django.db.models.loading import get_apps, load_app
- def _set_autocommit(self, connection):
- """
- Make sure a connection is in autocommit mode.
- """
- if hasattr(connection.connection, "autocommit"):
- connection.connection.autocommit(True)
- elif hasattr(connection.connection, "set_isolation_level"):
- connection.connection.set_isolation_level(0)
+ old_installed_apps = settings.INSTALLED_APPS
+ old_test_database_name = settings.TEST_DATABASE_NAME
+ old_root_urlconf = settings.ROOT_URLCONF
+ old_template_dirs = settings.TEMPLATE_DIRS
+
+ # Redirect some settings for the duration of these tests
+ settings.TEST_DATABASE_NAME = TEST_DATABASE_NAME
+ settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS
+ settings.ROOT_URLCONF = 'urls'
+ settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), TEST_TEMPLATE_DIR),)
+
+ # load all the ALWAYS_INSTALLED_APPS
+ get_apps()
+
+ # Load all the test model apps
+ test_models = []
+ for model_dir, model_name in get_test_models():
+ model_label = '.'.join([model_dir, model_name])
+ try:
+ # if the model was named on the command line, or
+ # no models were named (i.e., run all), import
+ # this model and add it to the list to test.
+ if not tests_to_run or model_name in tests_to_run:
+ if verbosity >= 1:
+ print "Importing model %s" % model_name
+ mod = load_app(model_label)
+ settings.INSTALLED_APPS.append(model_label)
+ test_models.append(mod)
+ except Exception, e:
+ sys.stderr.write("Error while importing %s:" % model_name + ''.join(traceback.format_exception(*sys.exc_info())[1:]))
+ continue
+ # Add tests for invalid models
+ extra_tests = []
+ for model_dir, model_name in get_invalid_models():
+ model_label = '.'.join([model_dir, model_name])
+ if not tests_to_run or model_name in tests_to_run:
+ extra_tests.append(InvalidModelTestCase(model_label))
+
+ # Run the test suite, including the extra validation tests.
+ from django.test.simple import run_tests
+ run_tests(test_models, verbosity, extra_tests=extra_tests)
+
+ # Restore the old settings
+ settings.INSTALLED_APPS = old_installed_apps
+ settings.TESTS_DATABASE_NAME = old_test_database_name
+ settings.ROOT_URLCONF = old_root_urlconf
+ settings.TEMPLATE_DIRS = old_template_dirs
+
if __name__ == "__main__":
from optparse import OptionParser
usage = "%prog [options] [model model model ...]"
parser = OptionParser(usage=usage)
- parser.add_option('-v', help='How verbose should the output be? Choices are 0, 1 and 2, where 2 is most verbose. Default is 0.',
- type='choice', choices=['0', '1', '2'])
+ parser.add_option('-v','--verbosity', action='store', dest='verbosity', default='0',
+ type='choice', choices=['0', '1', '2'],
+ help='Verbosity level; 0=minimal output, 1=normal output, 2=all output')
parser.add_option('--settings',
help='Python path to settings module, e.g. "myproject.settings". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.')
options, args = parser.parse_args()
- verbosity_level = 0
- if options.v:
- verbosity_level = int(options.v)
if options.settings:
os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
- t = TestRunner(verbosity_level, args)
- t.run_tests()
+
+ django_tests(int(options.verbosity), args)
diff --git a/tests/templates/404.html b/tests/templates/404.html
new file mode 100644
index 0000000000..da627e2222
--- /dev/null
+++ b/tests/templates/404.html
@@ -0,0 +1 @@
+Django Internal Tests: 404 Error \ No newline at end of file
diff --git a/tests/templates/500.html b/tests/templates/500.html
new file mode 100644
index 0000000000..ff028cbeb0
--- /dev/null
+++ b/tests/templates/500.html
@@ -0,0 +1 @@
+Django Internal Tests: 500 Error \ No newline at end of file
diff --git a/tests/templates/login.html b/tests/templates/login.html
new file mode 100644
index 0000000000..8a0974c9a1
--- /dev/null
+++ b/tests/templates/login.html
@@ -0,0 +1,19 @@
+<html>
+<head></head>
+<body>
+<h1>Django Internal Tests: Login</h1>
+{% if form.has_errors %}
+<p>Your username and password didn't match. Please try again.</p>
+{% endif %}
+
+<form method="post" action=".">
+<table>
+<tr><td><label for="id_username">Username:</label></td><td>{{ form.username }}</td></tr>
+<tr><td><label for="id_password">Password:</label></td><td>{{ form.password }}</td></tr>
+</table>
+
+<input type="submit" value="login" />
+<input type="hidden" name="next" value="{{ next }}" />
+</form>
+</body>
+</html> \ No newline at end of file
diff --git a/tests/urls.py b/tests/urls.py
new file mode 100644
index 0000000000..39d5aaee6b
--- /dev/null
+++ b/tests/urls.py
@@ -0,0 +1,10 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+ # test_client modeltest urls
+ (r'^test_client/', include('modeltests.test_client.urls')),
+
+ # Always provide the auth system login and logout views
+ (r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}),
+ (r'^accounts/logout/$', 'django.contrib.auth.views.login'),
+)