1import common
2import os, doctest, glob, sys
3from django.conf import settings
4from django.db import connection
5import django.test.utils
6
7# doctest takes a copy+paste log of a Python interactive session, runs a Python
8# interpreter, and replays all the inputs from the log, checking that the
9# outputs all match the log.  This allows us to easily test behavior and
10# document functions at the same time, since the log shows exactly how functions
11# are called and what their outputs look like.  See
12# http://www.python.org/doc/2.4.3/lib/module-doctest.html for more details.
13
14# In this file, we run doctest on all files found in the doctests/ directory.
15# We use django.test.utils to run the tests against a fresh test database every
16# time.
17
18class DoctestRunner(object):
19    _PRINT_AFTER = 'Ran %d tests from %s'
20
21    def __init__(self, app_dir, app_module_name):
22        self._app_dir = app_dir
23        self._app_module_name = app_module_name
24
25
26    def _get_doctest_paths(self):
27        doctest_dir = os.path.join(self._app_dir, 'doctests')
28        doctest_paths = [os.path.join(doctest_dir, filename) for filename
29                         in os.listdir(doctest_dir)
30                         if not filename.startswith('.')
31                         if not filename.endswith('~')]
32        return sorted(doctest_paths)
33
34
35    def _get_modules(self):
36        modules = []
37        module_names = [os.path.basename(filename)[:-3]
38                        for filename
39                        in glob.glob(os.path.join(self._app_dir, '*.py'))
40                        if '__init__' not in filename
41                        and 'test.py' not in filename]
42        # TODO: use common.setup_modules.import_module()
43        app_module = __import__(self._app_module_name, globals(), locals(),
44                                module_names)
45        for module_name in module_names:
46            modules.append(getattr(app_module, module_name))
47        return modules
48
49
50    def run_tests(self):
51        """
52        module_list is ignored - we're just required to have this signature as a
53        Django test runner.
54        """
55        doctest_paths = self._get_doctest_paths()
56        modules = self._get_modules()
57        total_errors = 0
58        old_db = settings.DATABASES['default']['NAME']
59        django.test.utils.setup_test_environment()
60        connection.creation.create_test_db()
61        try:
62            for module in modules:
63                failures, test_count = doctest.testmod(module)
64                print self._PRINT_AFTER % (test_count, module.__name__)
65                total_errors += failures
66            for path in doctest_paths:
67                failures, test_count = doctest.testfile(path,
68                                                        module_relative=False)
69                print self._PRINT_AFTER % (test_count, path)
70                total_errors += failures
71        finally:
72            connection.creation.destroy_test_db(old_db)
73            django.test.utils.teardown_test_environment()
74        print
75        if total_errors == 0:
76            print 'OK'
77        else:
78            print 'FAIL: %d errors' % total_errors
79        return total_errors
80