4 # Author: Lea Wiemann <LeWiemann@gmail.com>
5 # Copyright: This module has been placed in the public domain.
8 Perform tests with the data in the functional/ directory.
10 Read README.txt for details on how this is done.
19 import DocutilsTestSupport
# must be imported before docutils
24 datadir
= 'functional'
25 """The directory to store the data needed for the functional tests."""
29 return '/'.join(args
) or '.'
32 class FunctionalTestSuite(DocutilsTestSupport
.CustomTestSuite
):
34 """Test suite containing test cases for all config files."""
37 """Process all config files in functional/tests/."""
38 DocutilsTestSupport
.CustomTestSuite
.__init
__(self
)
39 os
.chdir(DocutilsTestSupport
.testroot
)
40 self
.clear_output_directory()
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.
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
)
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'))
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
):
68 def walker(self
, dummy
, dirname
, names
):
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.
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
)
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:
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:
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
120 """Process self.configfile."""
121 os
.chdir(DocutilsTestSupport
.testroot
)
122 # Keyword parameters for publish_file:
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('_'):
157 # Get output (automatically written to the output/ directory
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):
164 output
= output
.decode(output_encoding
)
165 except UnicodeDecodeError:
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())
181 if sys
.version_info
< (3,0):
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']}
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
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'),
208 return FunctionalTestSuite()
211 if __name__
== '__main__':
212 unittest
.main(defaultTest
='suite')