1"""Tests for distutils.filelist."""
2import os
3import re
4import unittest
5from distutils import debug
6from distutils.log import WARN
7from distutils.errors import DistutilsTemplateError
8from distutils.filelist import glob_to_re, translate_pattern, FileList
9from distutils import filelist
10
11import test.support
12from test.support import captured_stdout, run_unittest
13from distutils.tests import support
14
15MANIFEST_IN = """\
16include ok
17include xo
18exclude xo
19include foo.tmp
20include buildout.cfg
21global-include *.x
22global-include *.txt
23global-exclude *.tmp
24recursive-include f *.oo
25recursive-exclude global *.x
26graft dir
27prune dir3
28"""
29
30
31def make_local_path(s):
32    """Converts '/' in a string to os.sep"""
33    return s.replace('/', os.sep)
34
35
36class FileListTestCase(support.LoggingSilencer,
37                       unittest.TestCase):
38
39    def assertNoWarnings(self):
40        self.assertEqual(self.get_logs(WARN), [])
41        self.clear_logs()
42
43    def assertWarnings(self):
44        self.assertGreater(len(self.get_logs(WARN)), 0)
45        self.clear_logs()
46
47    def test_glob_to_re(self):
48        sep = os.sep
49        if os.sep == '\\':
50            sep = re.escape(os.sep)
51
52        for glob, regex in (
53            # simple cases
54            ('foo*', r'(?s:foo[^%(sep)s]*)\Z'),
55            ('foo?', r'(?s:foo[^%(sep)s])\Z'),
56            ('foo??', r'(?s:foo[^%(sep)s][^%(sep)s])\Z'),
57            # special cases
58            (r'foo\\*', r'(?s:foo\\\\[^%(sep)s]*)\Z'),
59            (r'foo\\\*', r'(?s:foo\\\\\\[^%(sep)s]*)\Z'),
60            ('foo????', r'(?s:foo[^%(sep)s][^%(sep)s][^%(sep)s][^%(sep)s])\Z'),
61            (r'foo\\??', r'(?s:foo\\\\[^%(sep)s][^%(sep)s])\Z')):
62            regex = regex % {'sep': sep}
63            self.assertEqual(glob_to_re(glob), regex)
64
65    def test_process_template_line(self):
66        # testing  all MANIFEST.in template patterns
67        file_list = FileList()
68        l = make_local_path
69
70        # simulated file list
71        file_list.allfiles = ['foo.tmp', 'ok', 'xo', 'four.txt',
72                              'buildout.cfg',
73                              # filelist does not filter out VCS directories,
74                              # it's sdist that does
75                              l('.hg/last-message.txt'),
76                              l('global/one.txt'),
77                              l('global/two.txt'),
78                              l('global/files.x'),
79                              l('global/here.tmp'),
80                              l('f/o/f.oo'),
81                              l('dir/graft-one'),
82                              l('dir/dir2/graft2'),
83                              l('dir3/ok'),
84                              l('dir3/sub/ok.txt'),
85                             ]
86
87        for line in MANIFEST_IN.split('\n'):
88            if line.strip() == '':
89                continue
90            file_list.process_template_line(line)
91
92        wanted = ['ok',
93                  'buildout.cfg',
94                  'four.txt',
95                  l('.hg/last-message.txt'),
96                  l('global/one.txt'),
97                  l('global/two.txt'),
98                  l('f/o/f.oo'),
99                  l('dir/graft-one'),
100                  l('dir/dir2/graft2'),
101                 ]
102
103        self.assertEqual(file_list.files, wanted)
104
105    def test_debug_print(self):
106        file_list = FileList()
107        with captured_stdout() as stdout:
108            file_list.debug_print('xxx')
109        self.assertEqual(stdout.getvalue(), '')
110
111        debug.DEBUG = True
112        try:
113            with captured_stdout() as stdout:
114                file_list.debug_print('xxx')
115            self.assertEqual(stdout.getvalue(), 'xxx\n')
116        finally:
117            debug.DEBUG = False
118
119    def test_set_allfiles(self):
120        file_list = FileList()
121        files = ['a', 'b', 'c']
122        file_list.set_allfiles(files)
123        self.assertEqual(file_list.allfiles, files)
124
125    def test_remove_duplicates(self):
126        file_list = FileList()
127        file_list.files = ['a', 'b', 'a', 'g', 'c', 'g']
128        # files must be sorted beforehand (sdist does it)
129        file_list.sort()
130        file_list.remove_duplicates()
131        self.assertEqual(file_list.files, ['a', 'b', 'c', 'g'])
132
133    def test_translate_pattern(self):
134        # not regex
135        self.assertTrue(hasattr(
136            translate_pattern('a', anchor=True, is_regex=False),
137            'search'))
138
139        # is a regex
140        regex = re.compile('a')
141        self.assertEqual(
142            translate_pattern(regex, anchor=True, is_regex=True),
143            regex)
144
145        # plain string flagged as regex
146        self.assertTrue(hasattr(
147            translate_pattern('a', anchor=True, is_regex=True),
148            'search'))
149
150        # glob support
151        self.assertTrue(translate_pattern(
152            '*.py', anchor=True, is_regex=False).search('filelist.py'))
153
154    def test_exclude_pattern(self):
155        # return False if no match
156        file_list = FileList()
157        self.assertFalse(file_list.exclude_pattern('*.py'))
158
159        # return True if files match
160        file_list = FileList()
161        file_list.files = ['a.py', 'b.py']
162        self.assertTrue(file_list.exclude_pattern('*.py'))
163
164        # test excludes
165        file_list = FileList()
166        file_list.files = ['a.py', 'a.txt']
167        file_list.exclude_pattern('*.py')
168        self.assertEqual(file_list.files, ['a.txt'])
169
170    def test_include_pattern(self):
171        # return False if no match
172        file_list = FileList()
173        file_list.set_allfiles([])
174        self.assertFalse(file_list.include_pattern('*.py'))
175
176        # return True if files match
177        file_list = FileList()
178        file_list.set_allfiles(['a.py', 'b.txt'])
179        self.assertTrue(file_list.include_pattern('*.py'))
180
181        # test * matches all files
182        file_list = FileList()
183        self.assertIsNone(file_list.allfiles)
184        file_list.set_allfiles(['a.py', 'b.txt'])
185        file_list.include_pattern('*')
186        self.assertEqual(file_list.allfiles, ['a.py', 'b.txt'])
187
188    def test_process_template(self):
189        l = make_local_path
190        # invalid lines
191        file_list = FileList()
192        for action in ('include', 'exclude', 'global-include',
193                       'global-exclude', 'recursive-include',
194                       'recursive-exclude', 'graft', 'prune', 'blarg'):
195            self.assertRaises(DistutilsTemplateError,
196                              file_list.process_template_line, action)
197
198        # include
199        file_list = FileList()
200        file_list.set_allfiles(['a.py', 'b.txt', l('d/c.py')])
201
202        file_list.process_template_line('include *.py')
203        self.assertEqual(file_list.files, ['a.py'])
204        self.assertNoWarnings()
205
206        file_list.process_template_line('include *.rb')
207        self.assertEqual(file_list.files, ['a.py'])
208        self.assertWarnings()
209
210        # exclude
211        file_list = FileList()
212        file_list.files = ['a.py', 'b.txt', l('d/c.py')]
213
214        file_list.process_template_line('exclude *.py')
215        self.assertEqual(file_list.files, ['b.txt', l('d/c.py')])
216        self.assertNoWarnings()
217
218        file_list.process_template_line('exclude *.rb')
219        self.assertEqual(file_list.files, ['b.txt', l('d/c.py')])
220        self.assertWarnings()
221
222        # global-include
223        file_list = FileList()
224        file_list.set_allfiles(['a.py', 'b.txt', l('d/c.py')])
225
226        file_list.process_template_line('global-include *.py')
227        self.assertEqual(file_list.files, ['a.py', l('d/c.py')])
228        self.assertNoWarnings()
229
230        file_list.process_template_line('global-include *.rb')
231        self.assertEqual(file_list.files, ['a.py', l('d/c.py')])
232        self.assertWarnings()
233
234        # global-exclude
235        file_list = FileList()
236        file_list.files = ['a.py', 'b.txt', l('d/c.py')]
237
238        file_list.process_template_line('global-exclude *.py')
239        self.assertEqual(file_list.files, ['b.txt'])
240        self.assertNoWarnings()
241
242        file_list.process_template_line('global-exclude *.rb')
243        self.assertEqual(file_list.files, ['b.txt'])
244        self.assertWarnings()
245
246        # recursive-include
247        file_list = FileList()
248        file_list.set_allfiles(['a.py', l('d/b.py'), l('d/c.txt'),
249                                l('d/d/e.py')])
250
251        file_list.process_template_line('recursive-include d *.py')
252        self.assertEqual(file_list.files, [l('d/b.py'), l('d/d/e.py')])
253        self.assertNoWarnings()
254
255        file_list.process_template_line('recursive-include e *.py')
256        self.assertEqual(file_list.files, [l('d/b.py'), l('d/d/e.py')])
257        self.assertWarnings()
258
259        # recursive-exclude
260        file_list = FileList()
261        file_list.files = ['a.py', l('d/b.py'), l('d/c.txt'), l('d/d/e.py')]
262
263        file_list.process_template_line('recursive-exclude d *.py')
264        self.assertEqual(file_list.files, ['a.py', l('d/c.txt')])
265        self.assertNoWarnings()
266
267        file_list.process_template_line('recursive-exclude e *.py')
268        self.assertEqual(file_list.files, ['a.py', l('d/c.txt')])
269        self.assertWarnings()
270
271        # graft
272        file_list = FileList()
273        file_list.set_allfiles(['a.py', l('d/b.py'), l('d/d/e.py'),
274                                l('f/f.py')])
275
276        file_list.process_template_line('graft d')
277        self.assertEqual(file_list.files, [l('d/b.py'), l('d/d/e.py')])
278        self.assertNoWarnings()
279
280        file_list.process_template_line('graft e')
281        self.assertEqual(file_list.files, [l('d/b.py'), l('d/d/e.py')])
282        self.assertWarnings()
283
284        # prune
285        file_list = FileList()
286        file_list.files = ['a.py', l('d/b.py'), l('d/d/e.py'), l('f/f.py')]
287
288        file_list.process_template_line('prune d')
289        self.assertEqual(file_list.files, ['a.py', l('f/f.py')])
290        self.assertNoWarnings()
291
292        file_list.process_template_line('prune e')
293        self.assertEqual(file_list.files, ['a.py', l('f/f.py')])
294        self.assertWarnings()
295
296
297class FindAllTestCase(unittest.TestCase):
298    @test.support.skip_unless_symlink
299    def test_missing_symlink(self):
300        with test.support.temp_cwd():
301            os.symlink('foo', 'bar')
302            self.assertEqual(filelist.findall(), [])
303
304    def test_basic_discovery(self):
305        """
306        When findall is called with no parameters or with
307        '.' as the parameter, the dot should be omitted from
308        the results.
309        """
310        with test.support.temp_cwd():
311            os.mkdir('foo')
312            file1 = os.path.join('foo', 'file1.txt')
313            test.support.create_empty_file(file1)
314            os.mkdir('bar')
315            file2 = os.path.join('bar', 'file2.txt')
316            test.support.create_empty_file(file2)
317            expected = [file2, file1]
318            self.assertEqual(sorted(filelist.findall()), expected)
319
320    def test_non_local_discovery(self):
321        """
322        When findall is called with another path, the full
323        path name should be returned.
324        """
325        with test.support.temp_dir() as temp_dir:
326            file1 = os.path.join(temp_dir, 'file1.txt')
327            test.support.create_empty_file(file1)
328            expected = [file1]
329            self.assertEqual(filelist.findall(temp_dir), expected)
330
331
332def test_suite():
333    return unittest.TestSuite([
334        unittest.makeSuite(FileListTestCase),
335        unittest.makeSuite(FindAllTestCase),
336    ])
337
338
339if __name__ == "__main__":
340    run_unittest(test_suite())
341