Update policies on branches and versioning.
[docutils.git] / docutils / test / test_functional.py
blob4d8a26edfffbb417966ac7388adab28aa1239591
1 #!/usr/bin/env python
3 # $Id$
4 # Author: Lea Wiemann <LeWiemann@gmail.com>
5 # Copyright: This module has been placed in the public domain.
7 """
8 Perform tests with the data in the functional/ directory.
10 Read README.txt for details on how this is done.
11 """
13 import sys
14 import os
15 import os.path
16 import shutil
17 import unittest
18 import difflib
19 import DocutilsTestSupport # must be imported before docutils
20 import docutils
21 import docutils.core
24 datadir = 'functional'
25 """The directory to store the data needed for the functional tests."""
28 def join_path(*args):
29 return '/'.join(args) or '.'
32 class FunctionalTestSuite(DocutilsTestSupport.CustomTestSuite):
34 """Test suite containing test cases for all config files."""
36 def __init__(self):
37 """Process all config files in functional/tests/."""
38 DocutilsTestSupport.CustomTestSuite.__init__(self)
39 os.chdir(DocutilsTestSupport.testroot)
40 self.clear_output_directory()
41 self.added = 0
42 try:
43 for root, dirs, files in os.walk(join_path(datadir, 'tests')):
44 # Process all config files among `names` in `dirname`. A config
45 # file is a Python file (*.py) which sets several variables.
46 for name in files:
47 if name.endswith('.py') and not name.startswith('_'):
48 config_file_full_path = join_path(root, name)
49 self.addTestCase(FunctionalTestCase, 'test', None, None,
50 id=config_file_full_path,
51 configfile=config_file_full_path)
52 self.added += 1
53 except (AttributeError): # python2.2 does not have os.walk
54 os.path.walk(join_path(datadir, 'tests'), self.walker, None)
55 assert self.added, 'No functional tests found.'
57 def clear_output_directory(self):
58 files = os.listdir(os.path.join('functional', 'output'))
59 for f in files:
60 if f in ('README.txt', '.svn', 'CVS'):
61 continue # don't touch the infrastructure
62 path = os.path.join('functional', 'output', f)
63 if os.path.isdir(path):
64 shutil.rmtree(path)
65 else:
66 os.remove(path)
68 def walker(self, dummy, dirname, names):
69 """
70 Process all config files among `names` in `dirname`.
72 This is a helper function for os.path.walk. A config file is
73 a Python file (*.py) which sets several variables.
74 """
75 for name in names:
76 if name.endswith('.py') and not name.startswith('_'):
77 config_file_full_path = join_path(dirname, name)
78 self.addTestCase(FunctionalTestCase, 'test', None, None,
79 id=config_file_full_path,
80 configfile=config_file_full_path)
81 self.added += 1
84 class FunctionalTestCase(DocutilsTestSupport.CustomTestCase):
86 """Test case for one config file."""
88 no_expected_template = """\
89 Cannot find expected output at %(exp)s
90 If the output in %(out)s
91 is correct, move it to the expected/ dir and check it in:
93 mv %(out)s %(exp)s
94 svn add %(exp)s
95 svn commit -m "<comment>" %(exp)s"""
97 expected_output_differs_template = """\
98 The expected and actual output differs.
99 Please compare the expected and actual output files:
101 diff %(exp)s %(out)s\n'
103 If the actual output is correct, please replace the
104 expected output and check it in:
106 mv %(out)s %(exp)s
107 svn add %(exp)s
108 svn commit -m "<comment>" %(exp)s"""
110 def __init__(self, *args, **kwargs):
111 """Set self.configfile, pass arguments to parent __init__."""
112 self.configfile = kwargs['configfile']
113 del kwargs['configfile']
114 DocutilsTestSupport.CustomTestCase.__init__(self, *args, **kwargs)
116 def shortDescription(self):
117 return 'test_functional.py: ' + self.configfile
119 def test(self):
120 """Process self.configfile."""
121 os.chdir(DocutilsTestSupport.testroot)
122 # Keyword parameters for publish_file:
123 namespace = {}
124 # Initialize 'settings_overrides' for test settings scripts,
125 # and disable configuration files:
126 namespace['settings_overrides'] = {'_disable_config': True}
127 # Read the variables set in the default config file and in
128 # the current config file into namespace:
129 defaultpy = open(join_path(datadir, 'tests', '_default.py')).read()
130 exec(defaultpy, namespace)
131 exec(open(self.configfile).read(), namespace)
132 # Check for required settings:
133 assert 'test_source' in namespace,\
134 "No 'test_source' supplied in " + self.configfile
135 assert 'test_destination' in namespace,\
136 "No 'test_destination' supplied in " + self.configfile
137 # Set source_path and destination_path if not given:
138 namespace.setdefault('source_path',
139 join_path(datadir, 'input',
140 namespace['test_source']))
141 # Path for actual output:
142 namespace.setdefault('destination_path',
143 join_path(datadir, 'output',
144 namespace['test_destination']))
145 # Path for expected output:
146 expected_path = join_path(datadir, 'expected',
147 namespace['test_destination'])
148 # shallow copy of namespace to minimize:
149 params = namespace.copy()
150 # remove unneeded parameters:
151 del params['test_source']
152 del params['test_destination']
153 # Delete private stuff like params['__builtins__']:
154 for key in params.keys():
155 if key.startswith('_'):
156 del params[key]
157 # Get output (automatically written to the output/ directory
158 # by publish_file):
159 output = docutils.core.publish_file(**params)
160 # ensure output is unicode
161 output_encoding = params.get('output_encoding', 'utf-8')
162 if sys.version_info < (3,0):
163 try:
164 output = output.decode(output_encoding)
165 except UnicodeDecodeError:
166 # failsafe
167 output = output.decode('latin1', 'replace')
168 # Normalize line endings:
169 output = '\n'.join(output.splitlines())
170 # Get the expected output *after* writing the actual output.
171 no_expected = self.no_expected_template % {
172 'exp': expected_path, 'out': params['destination_path']}
173 self.assertTrue(os.access(expected_path, os.R_OK), no_expected)
174 if sys.version_info < (3,0):
175 f = open(expected_path, 'r')
176 else: # samples are UTF8 encoded. 'rb' leads to errors with Python 3!
177 f = open(expected_path, 'r', encoding='utf-8')
178 # Normalize line endings:
179 expected = '\n'.join(f.read().splitlines())
180 f.close()
181 if sys.version_info < (3,0):
182 try:
183 expected = expected.decode(output_encoding)
184 except UnicodeDecodeError:
185 expected = expected.decode('latin1', 'replace')
187 diff = self.expected_output_differs_template % {
188 'exp': expected_path, 'out': params['destination_path']}
189 try:
190 self.assertEqual(output, expected, diff)
191 except AssertionError:
192 diff = ''.join(difflib.unified_diff(
193 expected.splitlines(True), output.splitlines(True),
194 expected_path, params['destination_path']))
195 if sys.version_info < (3,0):
196 diff = diff.encode(sys.stderr.encoding or 'ascii', 'replace')
197 print >>sys.stderr, '\n%s:' % (self,)
198 print >>sys.stderr, diff
199 raise
200 # Execute optional function containing extra tests:
201 if '_test_more' in namespace:
202 namespace['_test_more'](join_path(datadir, 'expected'),
203 join_path(datadir, 'output'),
204 self, namespace)
207 def suite():
208 return FunctionalTestSuite()
211 if __name__ == '__main__':
212 unittest.main(defaultTest='suite')