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 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 = '' -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 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 ('' % - (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 - (?:^(?P [ ]*) >>> .*) # PS1 line - (?:\n [ ]* \.\.\. .*)*) # PS2 lines - \n? - # Want consists of any non-blank lines that do not start with PS1. - (?P (?:(?![ ]*$) # 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 Traceback\ \( - (?: most\ recent\ call\ last - | innermost\ last - ) \) : - ) - \s* $ # toss trailing whitespace on the header. - (?P .*?) # don't blink: absorb stuff until... - ^ (?P \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=''): - """ - 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=''): - """ - 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 = '' % (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'[\w\.]+)' - r'\[(?P\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 - - # 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 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 s are being used, then replace blank lines - # with 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: - - >>> 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 : - >>> print 'foo\n\nbar\n' - foo - - bar - - """, - - "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..acbea0d1e0 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() [] @@ -314,14 +313,14 @@ AttributeError: Manager isn't accessible via Article instances >>> Article.objects.all() [, , , ] -""" +"""} 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 +329,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 +338,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') [, ] -""" +"""} 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 [] >>> b.articles_from_same_day_2() [] -""" +"""} diff --git a/tests/modeltests/custom_pk/models.py b/tests/modeltests/custom_pk/models.py index f7b790ca21..ca788f6aa5 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() @@ -88,4 +88,4 @@ DoesNotExist: Employee matching query does not exist. >>> Business.objects.filter(employees__first_name__startswith='Fran') [] -""" +"""} diff --git a/tests/modeltests/empty/models.py b/tests/modeltests/empty/models.py index 0f5a0b870f..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() @@ -23,4 +23,4 @@ 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.objects.filter(content_type__pk=ctype.id, object_id=quartz.id) [] -""" +"""} diff --git a/tests/modeltests/get_latest/models.py b/tests/modeltests/get_latest/models.py index 42e7a14ec7..ff8d8f28eb 100644 --- a/tests/modeltests/get_latest/models.py +++ b/tests/modeltests/get_latest/models.py @@ -29,7 +29,7 @@ class Person(models.Model): def __str__(self): return self.name -API_TESTS = """ +__test__ = {'API_TESTS':""" # Because no Articles exist yet, get_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') -""" +"""} 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..5540c1bd5f 100644 --- a/tests/modeltests/invalid_models/models.py +++ b/tests/modeltests/invalid_models/models.py @@ -78,7 +78,7 @@ class SelfClashM2M(models.Model): -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. diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py index 55bb373a4b..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 = r""" +__test__ = {'API_TESTS':r""" # Create a couple of Articles. >>> from datetime import datetime >>> a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26)) @@ -191,4 +191,4 @@ DoesNotExist: Article matching query does not exist. >>> Article.objects.filter(headline__contains='\\') [] -""" +"""} 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.objects.filter(Q(client=r.id) | Q(cc__id__exact=r.id)) [, , ] -""" +"""} 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 = """ >>> r1.writer_set.all() [] -""" +"""} 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() [, ] -""" +"""} 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() [] -""" +"""} 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 -""" +"""} 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 >>> 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() [] -""" +"""} 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: is not related to . >>> Article.objects.filter(reporter__isnull=True) [, ] -""" +"""} 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..8afa74454d 100644 --- a/tests/modeltests/one_to_one/models.py +++ b/tests/modeltests/one_to_one/models.py @@ -30,7 +30,7 @@ class Waiter(models.Model): def __str__(self): return "%s the waiter at %s" % (self.name, self.restaurant) -API_TESTS = """ +__test__ = {'API_TESTS':""" # Create a couple of Places. >>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton') >>> p1.save() @@ -151,4 +151,4 @@ DoesNotExist: Restaurant matching query does not exist. # Delete the restaurant; the waiter should also be removed >>> r = Restaurant.objects.get(pk=1) >>> r.delete() -""" +"""} 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.objects.complex_filter(Q(pk=1) | Q(pk=2)) [, ] -""" +"""} 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) [] -""" +"""} 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() [, ] -""" +"""} 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 -""" +"""} 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 2b00a8c7a5..0000000000 --- a/tests/othertests/markup.py +++ /dev/null @@ -1,70 +0,0 @@ -# Quick tests for the markup templatetags (django.contrib.markup) - -from django.template import Template, Context, add_to_builtins -import re - -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 == """

Paragraph 1

- -

Paragraph 2 with “quotes” and code

""" -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: - pattern = re.compile("""

Paragraph 1\s*

\s*

\s*An h2

""") - assert pattern.match(rendered) -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 =="""

Paragraph 1

-

Paragraph 2 with a link

""" -else: - assert rendered == rest_content diff --git a/tests/othertests/templates.py b/tests/othertests/templates.py deleted file mode 100644 index 9975f3b05c..0000000000 --- a/tests/othertests/templates.py +++ /dev/null @@ -1,634 +0,0 @@ -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 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": "Yes"}, "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_'), - - # 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 %} text {% endspaceless %}", {}, " text "), - 'spaceless02': ("{% spaceless %} \n text \n {% endspaceless %}", {}, " text "), - 'spaceless03': ("{% spaceless %}text{% endspaceless %}", {}, "text"), - - # 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, 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'), -} - -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__": - run_tests(1, True) diff --git a/tests/othertests/__init__.py b/tests/regressiontests/cache/__init__.py similarity index 100% rename from tests/othertests/__init__.py rename to 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 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 diff --git a/tests/regressiontests/dateformat/models.py b/tests/regressiontests/dateformat/models.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/othertests/dateformat.py b/tests/regressiontests/dateformat/tests.py similarity index 100% rename from tests/othertests/dateformat.py rename to tests/regressiontests/dateformat/tests.py diff --git a/tests/regressiontests/db_typecasts/__init__.py b/tests/regressiontests/db_typecasts/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regressiontests/db_typecasts/models.py b/tests/regressiontests/db_typecasts/models.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/othertests/db_typecasts.py b/tests/regressiontests/db_typecasts/tests.py similarity index 81% rename from tests/othertests/db_typecasts.py rename to 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 diff --git a/tests/regressiontests/defaultfilters/models.py b/tests/regressiontests/defaultfilters/models.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/othertests/defaultfilters.py b/tests/regressiontests/defaultfilters/tests.py similarity index 100% rename from tests/othertests/defaultfilters.py rename to tests/regressiontests/defaultfilters/tests.py diff --git a/tests/regressiontests/httpwrappers/__init__.py b/tests/regressiontests/httpwrappers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regressiontests/httpwrappers/models.py b/tests/regressiontests/httpwrappers/models.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/othertests/httpwrappers.py b/tests/regressiontests/httpwrappers/tests.py similarity index 100% rename from tests/othertests/httpwrappers.py rename to 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/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 diff --git a/tests/regressiontests/markup/models.py b/tests/regressiontests/markup/models.py new file mode 100644 index 0000000000..e69de29bb2 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, """

Paragraph 1

+ +

Paragraph 2 with “quotes” and code

""") + 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("""

Paragraph 1\s*

\s*

\s*An 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, """

Paragraph 1

+

Paragraph 2 with a link

""") + else: + self.assertEqual(rendered, rest_content) + + +if __name__ == '__main__': + unittest.main() 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() [] -""" +"""} 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 -""" +"""} diff --git a/tests/regressiontests/templates/__init__.py b/tests/regressiontests/templates/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regressiontests/templates/models.py b/tests/regressiontests/templates/models.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py new file mode 100644 index 0000000000..2d1ce192ef --- /dev/null +++ b/tests/regressiontests/templates/tests.py @@ -0,0 +1,621 @@ +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", {}, "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": "Yes"}, "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_'), + + # 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 %} text {% endspaceless %}", {}, " text "), + 'spaceless02': ("{% spaceless %} \n text \n {% endspaceless %}", {}, " text "), + 'spaceless03': ("{% spaceless %}text{% endspaceless %}", {}, "text"), + + # 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, 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 = 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]: + failures.append("Template test: %s -- FAILED. Got %s, exception: %s" % (name, e.__class__, e)) + continue + if 'LANGUAGE_CODE' in vals[1]: + deactivate() + if output != vals[2]: + failures.append("Template test: %s -- FAILED. Expected %r, got %r" % (name, vals[2], output)) + loader.template_source_loaders = old_template_loaders + deactivate() + settings.TEMPLATE_DEBUG = old_td + settings.TEMPLATE_STRING_IF_INVALID = old_invalid + + self.assertEqual(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 diff --git a/tests/regressiontests/urlpatterns_reverse/models.py b/tests/regressiontests/urlpatterns_reverse/models.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/othertests/urlpatterns_reverse.py b/tests/regressiontests/urlpatterns_reverse/tests.py similarity index 70% rename from tests/othertests/urlpatterns_reverse.py rename to tests/regressiontests/urlpatterns_reverse/tests.py index 236944d49f..8f571ac66c 100644 --- a/tests/othertests/urlpatterns_reverse.py +++ b/tests/regressiontests/urlpatterns_reverse/tests.py @@ -1,7 +1,7 @@ "Unit tests for reverse URL lookup" from django.core.urlresolvers import reverse_helper, NoReverseMatch -import re +import re, unittest test_data = ( ('^places/(\d+)/$', 'places/3/', [3], {}), @@ -25,23 +25,15 @@ test_data = ( ('^people/(?P\w\w)/(\w+)/$', 'people/il/adrian/', ['adrian'], {'state': 'il'}), ) -def run_tests(verbosity=0): - for regex, expected, args, kwargs in test_data: - passed = True - try: - got = reverse_helper(re.compile(regex), *args, **kwargs) - except NoReverseMatch, e: - if expected != NoReverseMatch: - passed, got = False, str(e) - else: - if got != expected: - passed, got = False, got - if passed and verbosity: - print "Passed: %s" % regex - elif not passed: - print "REVERSE LOOKUP FAILED: %s" % regex - print " Got: %s" % got - print " Expected: %r" % expected +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..57087ef3a7 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -1,23 +1,10 @@ #!/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, - }) 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 +24,101 @@ 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'(? 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 - 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:])) +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 + from django.db.models.loading import load_app + from cStringIO import StringIO - 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) + 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') - # 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)) + 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 - 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') + self.assert_(not unexpected, "Unexpected Errors: " + '\n'.join(unexpected)) + self.assert_(not missing, "Missing Errors: " + '\n'.join(missing)) - unexpected = [err for err in actual if err not in expected] - missing = [err for err in expected if err not in actual] - - 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)) - - 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 - - # 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) - - # 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 _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) +def django_tests(verbosity, tests_to_run): + from django.conf import settings + from django.db.models.loading import get_apps, load_app + old_installed_apps = settings.INSTALLED_APPS + + # load all the ALWAYS_INSTALLED_APPS + settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS + get_apps() + + test_models = [] + # Load all the test model apps + 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 INSTALLED_APPS setting + settings.INSTALLED_APPS = old_installed_apps + 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)