~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_globbing.py

  • Committer: Robert Collins
  • Date: 2005-12-02 03:23:47 UTC
  • Revision ID: robertc@robertcollins.net-20051202032347-ba08123207a945a0
Test for Jeff Bailey.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
2
 
# -*- coding: utf-8 -*-
3
 
#
4
 
# This program is free software; you can redistribute it and/or modify
5
 
# it under the terms of the GNU General Public License as published by
6
 
# the Free Software Foundation; either version 2 of the License, or
7
 
# (at your option) any later version.
8
 
#
9
 
# This program is distributed in the hope that it will be useful,
10
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 
# GNU General Public License for more details.
13
 
#
14
 
# You should have received a copy of the GNU General Public License
15
 
# along with this program; if not, write to the Free Software
16
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
 
 
18
 
import re
19
 
 
20
 
from bzrlib import errors
21
 
from bzrlib.globbing import (
22
 
    Globster,
23
 
    ExceptionGlobster,
24
 
    _OrderedGlobster,
25
 
    normalize_pattern
26
 
    )
27
 
from bzrlib.tests import (
28
 
    TestCase,
29
 
    TestCaseInTempDir,
30
 
    )
31
 
 
32
 
 
33
 
class TestGlobster(TestCase):
34
 
 
35
 
    def assertMatch(self, matchset, glob_prefix=None):
36
 
        for glob, positive, negative in matchset:
37
 
            if glob_prefix:
38
 
                glob = glob_prefix + glob
39
 
            globster = Globster([glob])
40
 
            for name in positive:
41
 
                self.failUnless(globster.match(name), repr(
42
 
                    u'name "%s" does not match glob "%s" (re=%s)' %
43
 
                    (name, glob, globster._regex_patterns[0][0].pattern)))
44
 
            for name in negative:
45
 
                self.failIf(globster.match(name), repr(
46
 
                    u'name "%s" does match glob "%s" (re=%s)' %
47
 
                    (name, glob, globster._regex_patterns[0][0].pattern)))
48
 
 
49
 
    def assertMatchBasenameAndFullpath(self, matchset):
50
 
        # test basename matcher
51
 
        self.assertMatch(matchset)
52
 
        # test fullpath matcher
53
 
        self.assertMatch(matchset, glob_prefix='./')
54
 
 
55
 
    def test_char_group_digit(self):
56
 
        self.assertMatchBasenameAndFullpath([
57
 
            # The definition of digit this uses includes arabic digits from
58
 
            # non-latin scripts (arabic, indic, etc.) but neither roman
59
 
            # numerals nor vulgar fractions. Some characters such as
60
 
            # subscript/superscript digits may or may not match depending on
61
 
            # the Python version used, see: <http://bugs.python.org/issue6561>
62
 
            (u'[[:digit:]]',
63
 
             [u'0', u'5', u'\u0663', u'\u06f9', u'\u0f21'],
64
 
             [u'T', u'q', u' ', u'\u8336', u'.']),
65
 
            (u'[^[:digit:]]',
66
 
             [u'T', u'q', u' ', u'\u8336', u'.'],
67
 
             [u'0', u'5', u'\u0663', u'\u06f9', u'\u0f21']),
68
 
            ])
69
 
 
70
 
    def test_char_group_space(self):
71
 
        self.assertMatchBasenameAndFullpath([
72
 
            (u'[[:space:]]',
73
 
             [u' ', u'\t', u'\n', u'\xa0', u'\u2000', u'\u2002'],
74
 
             [u'a', u'-', u'\u8336', u'.']),
75
 
            (u'[^[:space:]]',
76
 
             [u'a', u'-', u'\u8336', u'.'],
77
 
             [u' ', u'\t', u'\n', u'\xa0', u'\u2000', u'\u2002']),
78
 
            ])
79
 
 
80
 
    def test_char_group_alnum(self):
81
 
        self.assertMatchBasenameAndFullpath([
82
 
            (u'[[:alnum:]]',
83
 
             [u'a', u'Z', u'\u017e', u'\u8336'],
84
 
             [u':', u'-', u'\u25cf', u'.']),
85
 
            (u'[^[:alnum:]]',
86
 
             [u':', u'-', u'\u25cf', u'.'],
87
 
             [u'a']),
88
 
            ])
89
 
 
90
 
    def test_char_group_ascii(self):
91
 
        self.assertMatchBasenameAndFullpath([
92
 
            (u'[[:ascii:]]',
93
 
             [u'a', u'Q', u'^', u'.'],
94
 
             [u'\xcc', u'\u8336']),
95
 
            (u'[^[:ascii:]]',
96
 
             [u'\xcc', u'\u8336'],
97
 
             [u'a', u'Q', u'^', u'.']),
98
 
            ])
99
 
 
100
 
    def test_char_group_blank(self):
101
 
        self.assertMatchBasenameAndFullpath([
102
 
            (u'[[:blank:]]',
103
 
             [u'\t'],
104
 
             [u'x', u'y', u'z', u'.']),
105
 
            (u'[^[:blank:]]',
106
 
             [u'x', u'y', u'z', u'.'],
107
 
             [u'\t']),
108
 
            ])
109
 
 
110
 
    def test_char_group_cntrl(self):
111
 
        self.assertMatchBasenameAndFullpath([
112
 
            (u'[[:cntrl:]]',
113
 
             [u'\b', u'\t', '\x7f'],
114
 
             [u'a', u'Q', u'\u8336', u'.']),
115
 
            (u'[^[:cntrl:]]',
116
 
             [u'a', u'Q', u'\u8336', u'.'],
117
 
             [u'\b', u'\t', '\x7f']),
118
 
            ])
119
 
 
120
 
    def test_char_group_range(self):
121
 
        self.assertMatchBasenameAndFullpath([
122
 
            (u'[a-z]',
123
 
             [u'a', u'q', u'f'],
124
 
             [u'A', u'Q', u'F']),
125
 
            (u'[^a-z]',
126
 
             [u'A', u'Q', u'F'],
127
 
             [u'a', u'q', u'f']),
128
 
            (u'[!a-z]foo',
129
 
             [u'Afoo', u'.foo'],
130
 
             [u'afoo', u'ABfoo']),
131
 
            (u'foo[!a-z]bar',
132
 
             [u'fooAbar', u'foo.bar'],
133
 
             [u'foojbar']),
134
 
            (u'[\x20-\x30\u8336]',
135
 
             [u'\040', u'\044', u'\u8336'],
136
 
             [u'\x1f']),
137
 
            (u'[^\x20-\x30\u8336]',
138
 
             [u'\x1f'],
139
 
             [u'\040', u'\044', u'\u8336']),
140
 
            ])
141
 
 
142
 
    def test_regex(self):
143
 
        self.assertMatch([
144
 
            (u'RE:(a|b|c+)',
145
 
             [u'a', u'b', u'ccc'],
146
 
             [u'd', u'aa', u'c+', u'-a']),
147
 
            (u'RE:(?:a|b|c+)',
148
 
             [u'a', u'b', u'ccc'],
149
 
             [u'd', u'aa', u'c+', u'-a']),
150
 
            (u'RE:(?P<a>.)(?P=a)',
151
 
             [u'a'],
152
 
             [u'ab', u'aa', u'aaa']),
153
 
            # test we can handle odd numbers of trailing backslashes
154
 
            (u'RE:a\\\\\\',
155
 
             [u'a\\'],
156
 
             [u'a', u'ab', u'aa', u'aaa']),
157
 
            ])
158
 
 
159
 
    def test_question_mark(self):
160
 
        self.assertMatch([
161
 
            (u'?foo',
162
 
             [u'xfoo', u'bar/xfoo', u'bar/\u8336foo', u'.foo', u'bar/.foo'],
163
 
             [u'bar/foo', u'foo']),
164
 
            (u'foo?bar',
165
 
             [u'fooxbar', u'foo.bar', u'foo\u8336bar', u'qyzzy/foo.bar'],
166
 
             [u'foo/bar']),
167
 
            (u'foo/?bar',
168
 
             [u'foo/xbar', u'foo/\u8336bar', u'foo/.bar'],
169
 
             [u'foo/bar', u'bar/foo/xbar']),
170
 
            ])
171
 
 
172
 
    def test_asterisk(self):
173
 
        self.assertMatch([
174
 
            (u'x*x',
175
 
             [u'xx', u'x.x', u'x\u8336..x', u'\u8336/x.x', u'x.y.x'],
176
 
             [u'x/x', u'bar/x/bar/x', u'bax/abaxab']),
177
 
            (u'foo/*x',
178
 
             [u'foo/x', u'foo/bax', u'foo/a.x', u'foo/.x', u'foo/.q.x'],
179
 
             [u'foo/bar/bax']),
180
 
            (u'*/*x',
181
 
             [u'\u8336/x', u'foo/x', u'foo/bax', u'x/a.x', u'.foo/x',
182
 
              u'\u8336/.x', u'foo/.q.x'],
183
 
             [u'foo/bar/bax']),
184
 
            (u'f*',
185
 
             [u'foo', u'foo.bar'],
186
 
             [u'.foo', u'foo/bar', u'foo/.bar']),
187
 
            (u'*bar',
188
 
             [u'bar', u'foobar', ur'foo\nbar', u'foo.bar', u'foo/bar',
189
 
              u'foo/foobar', u'foo/f.bar', u'.bar', u'foo/.bar'],
190
 
             []),
191
 
            ])
192
 
 
193
 
    def test_double_asterisk(self):
194
 
        self.assertMatch([
195
 
            # expected uses of double asterisk
196
 
            (u'foo/**/x',
197
 
             [u'foo/x', u'foo/bar/x'],
198
 
             [u'foox', u'foo/bax', u'foo/.x', u'foo/bar/bax']),
199
 
            (u'**/bar',
200
 
             [u'bar', u'foo/bar'],
201
 
             [u'foobar', u'foo.bar', u'foo/foobar', u'foo/f.bar',
202
 
              u'.bar', u'foo/.bar']),
203
 
            # check that we ignore extra *s, so *** is treated like ** not *.
204
 
            (u'foo/***/x',
205
 
             [u'foo/x', u'foo/bar/x'],
206
 
             [u'foox', u'foo/bax', u'foo/.x', u'foo/bar/bax']),
207
 
            (u'***/bar',
208
 
             [u'bar', u'foo/bar'],
209
 
             [u'foobar', u'foo.bar', u'foo/foobar', u'foo/f.bar',
210
 
              u'.bar', u'foo/.bar']),
211
 
            # the remaining tests check that ** is interpreted as *
212
 
            # unless it is a whole path component
213
 
            (u'x**/x',
214
 
             [u'x\u8336/x', u'x/x'],
215
 
             [u'xx', u'x.x', u'bar/x/bar/x', u'x.y.x', u'x/y/x']),
216
 
            (u'x**x',
217
 
             [u'xx', u'x.x', u'x\u8336..x', u'foo/x.x', u'x.y.x'],
218
 
             [u'bar/x/bar/x', u'xfoo/bar/x', u'x/x', u'bax/abaxab']),
219
 
            (u'foo/**x',
220
 
             [u'foo/x', u'foo/bax', u'foo/a.x', u'foo/.x', u'foo/.q.x'],
221
 
             [u'foo/bar/bax']),
222
 
            (u'f**',
223
 
             [u'foo', u'foo.bar'],
224
 
             [u'.foo', u'foo/bar', u'foo/.bar']),
225
 
            (u'**bar',
226
 
             [u'bar', u'foobar', ur'foo\nbar', u'foo.bar', u'foo/bar',
227
 
              u'foo/foobar', u'foo/f.bar', u'.bar', u'foo/.bar'],
228
 
             []),
229
 
            ])
230
 
 
231
 
    def test_leading_dot_slash(self):
232
 
        self.assertMatch([
233
 
            (u'./foo',
234
 
             [u'foo'],
235
 
             [u'\u8336/foo', u'barfoo', u'x/y/foo']),
236
 
            (u'./f*',
237
 
             [u'foo'],
238
 
             [u'foo/bar', u'foo/.bar', u'x/foo/y']),
239
 
            ])
240
 
 
241
 
    def test_backslash(self):
242
 
        self.assertMatch([
243
 
            (u'.\\foo',
244
 
             [u'foo'],
245
 
             [u'\u8336/foo', u'barfoo', u'x/y/foo']),
246
 
            (u'.\\f*',
247
 
             [u'foo'],
248
 
             [u'foo/bar', u'foo/.bar', u'x/foo/y']),
249
 
            (u'foo\\**\\x',
250
 
             [u'foo/x', u'foo/bar/x'],
251
 
             [u'foox', u'foo/bax', u'foo/.x', u'foo/bar/bax']),
252
 
            ])
253
 
 
254
 
    def test_trailing_slash(self):
255
 
        self.assertMatch([
256
 
            (u'./foo/',
257
 
             [u'foo'],
258
 
             [u'\u8336/foo', u'barfoo', u'x/y/foo']),
259
 
            (u'.\\foo\\',
260
 
             [u'foo'],
261
 
             [u'foo/', u'\u8336/foo', u'barfoo', u'x/y/foo']),
262
 
            ])
263
 
 
264
 
    def test_leading_asterisk_dot(self):
265
 
        self.assertMatch([
266
 
            (u'*.x',
267
 
             [u'foo/bar/baz.x', u'\u8336/Q.x', u'foo.y.x', u'.foo.x',
268
 
              u'bar/.foo.x', u'.x',],
269
 
             [u'foo.x.y']),
270
 
            (u'foo/*.bar',
271
 
             [u'foo/b.bar', u'foo/a.b.bar', u'foo/.bar'],
272
 
             [u'foo/bar']),
273
 
            (u'*.~*',
274
 
             [u'foo.py.~1~', u'.foo.py.~1~'],
275
 
             []),
276
 
            ])
277
 
 
278
 
    def test_end_anchor(self):
279
 
        self.assertMatch([
280
 
            (u'*.333',
281
 
             [u'foo.333'],
282
 
             [u'foo.3']),
283
 
            (u'*.3',
284
 
             [u'foo.3'],
285
 
             [u'foo.333']),
286
 
            ])
287
 
 
288
 
    def test_mixed_globs(self):
289
 
        """tests handling of combinations of path type matches.
290
 
 
291
 
        The types being extension, basename and full path.
292
 
        """
293
 
        patterns = [ u'*.foo', u'.*.swp', u'./*.png']
294
 
        globster = Globster(patterns)
295
 
        self.assertEqual(u'*.foo', globster.match('bar.foo'))
296
 
        self.assertEqual(u'./*.png', globster.match('foo.png'))
297
 
        self.assertEqual(None, globster.match('foo/bar.png'))
298
 
        self.assertEqual(u'.*.swp', globster.match('foo/.bar.py.swp'))
299
 
 
300
 
    def test_large_globset(self):
301
 
        """tests that the globster can handle a large set of patterns.
302
 
 
303
 
        Large is defined as more than supported by python regex groups,
304
 
        i.e. 99.
305
 
        This test assumes the globs are broken into regexs containing 99
306
 
        groups.
307
 
        """
308
 
        patterns = [ u'*.%03d' % i for i in xrange(0,300) ]
309
 
        globster = Globster(patterns)
310
 
        # test the fence posts
311
 
        for x in (0,98,99,197,198,296,297,299):
312
 
            filename = u'foo.%03d' % x
313
 
            self.assertEqual(patterns[x],globster.match(filename))
314
 
        self.assertEqual(None,globster.match('foobar.300'))
315
 
 
316
 
    def test_bad_pattern(self):
317
 
        """Ensure that globster handles bad patterns cleanly."""
318
 
        patterns = [u'RE:[', u'/home/foo', u'RE:*.cpp']
319
 
        g = Globster(patterns)
320
 
        e = self.assertRaises(errors.InvalidPattern, g.match, 'filename')
321
 
        self.assertContainsRe(e.msg,
322
 
            "File.*ignore.*contains error.*RE:\[.*RE:\*\.cpp", flags=re.DOTALL)
323
 
 
324
 
 
325
 
class TestExceptionGlobster(TestCase):
326
 
 
327
 
    def test_exclusion_patterns(self):
328
 
        """test that exception patterns are not matched"""
329
 
        patterns = [ u'*', u'!./local', u'!./local/**/*', u'!RE:\.z.*',u'!!./.zcompdump' ]
330
 
        globster = ExceptionGlobster(patterns)
331
 
        self.assertEqual(u'*', globster.match('tmp/foo.txt'))
332
 
        self.assertEqual(None, globster.match('local'))
333
 
        self.assertEqual(None, globster.match('local/bin/wombat'))
334
 
        self.assertEqual(None, globster.match('.zshrc'))
335
 
        self.assertEqual(None, globster.match('.zfunctions/fiddle/flam'))
336
 
        self.assertEqual(u'!!./.zcompdump', globster.match('.zcompdump'))
337
 
 
338
 
    def test_exclusion_order(self):
339
 
        """test that ordering of exclusion patterns does not matter"""
340
 
        patterns = [ u'static/**/*.html', u'!static/**/versionable.html']
341
 
        globster = ExceptionGlobster(patterns)
342
 
        self.assertEqual(u'static/**/*.html', globster.match('static/foo.html'))
343
 
        self.assertEqual(None, globster.match('static/versionable.html'))
344
 
        self.assertEqual(None, globster.match('static/bar/versionable.html'))
345
 
        globster = ExceptionGlobster(reversed(patterns))
346
 
        self.assertEqual(u'static/**/*.html', globster.match('static/foo.html'))
347
 
        self.assertEqual(None, globster.match('static/versionable.html'))
348
 
        self.assertEqual(None, globster.match('static/bar/versionable.html'))
349
 
 
350
 
class TestOrderedGlobster(TestCase):
351
 
 
352
 
    def test_ordered_globs(self):
353
 
        """test that the first match in a list is the one found"""
354
 
        patterns = [ u'*.foo', u'bar.*']
355
 
        globster = _OrderedGlobster(patterns)
356
 
        self.assertEqual(u'*.foo', globster.match('bar.foo'))
357
 
        self.assertEqual(None, globster.match('foo.bar'))
358
 
        globster = _OrderedGlobster(reversed(patterns))
359
 
        self.assertEqual(u'bar.*', globster.match('bar.foo'))
360
 
        self.assertEqual(None, globster.match('foo.bar'))
361
 
 
362
 
 
363
 
class TestNormalizePattern(TestCase):
364
 
 
365
 
    def test_backslashes(self):
366
 
        """tests that backslashes are converted to forward slashes, multiple
367
 
        backslashes are collapsed to single forward slashes and trailing
368
 
        backslashes are removed"""
369
 
        self.assertEqual(u'/', normalize_pattern(u'\\'))
370
 
        self.assertEqual(u'/', normalize_pattern(u'\\\\'))
371
 
        self.assertEqual(u'/foo/bar', normalize_pattern(u'\\foo\\bar'))
372
 
        self.assertEqual(u'foo/bar', normalize_pattern(u'foo\\bar\\'))
373
 
        self.assertEqual(u'/foo/bar', normalize_pattern(u'\\\\foo\\\\bar\\\\'))
374
 
 
375
 
    def test_forward_slashes(self):
376
 
        """tests that multiple foward slashes are collapsed to single forward
377
 
        slashes and trailing forward slashes are removed"""
378
 
        self.assertEqual(u'/', normalize_pattern(u'/'))
379
 
        self.assertEqual(u'/', normalize_pattern(u'//'))
380
 
        self.assertEqual(u'/foo/bar', normalize_pattern(u'/foo/bar'))
381
 
        self.assertEqual(u'foo/bar', normalize_pattern(u'foo/bar/'))
382
 
        self.assertEqual(u'/foo/bar', normalize_pattern(u'//foo//bar//'))
383
 
 
384
 
    def test_mixed_slashes(self):
385
 
        """tests that multiple mixed slashes are collapsed to single forward
386
 
        slashes and trailing mixed slashes are removed"""
387
 
        self.assertEqual(u'/foo/bar', normalize_pattern(u'\\/\\foo//\\///bar/\\\\/'))