Merge lp:~aaron-whitehouse/duplicity/08-merge-glob-parsers into lp:~duplicity-team/duplicity/0.8-series

Proposed by Aaron Whitehouse
Status: Merged
Merged at revision: 1172
Proposed branch: lp:~aaron-whitehouse/duplicity/08-merge-glob-parsers
Merge into: lp:~duplicity-team/duplicity/0.8-series
Diff against target: 431 lines (+159/-135)
6 files modified
duplicity/globmatch.py (+13/-2)
duplicity/selection.py (+7/-66)
testing/functional/test_selection.py (+31/-0)
testing/run-tests-ve (+0/-23)
testing/unit/test_globmatch.py (+39/-23)
testing/unit/test_selection.py (+69/-21)
To merge this branch: bzr merge lp:~aaron-whitehouse/duplicity/08-merge-glob-parsers
Reviewer Review Type Date Requested Status
duplicity-team Pending
Review via email: mp+315851@code.launchpad.net

Commit message

* Use a single code path for glob strings whether or not these contain special characters/wildcards (glob_get_normal_sf) and remove glob_get_filename_sf and glob_get_tuple_sf.
* Remove run-tests-ve as this was identical to run-tests.

Description of the change

Move to using a single code path for glob strings whether or not these contain special characters/wildcards.

Note that a few test cases were changed in this patch, i.e. behaviour of the code has changed. In my view these changes were corrections, where the selection function was returning the incorrect value (e.g. an include, rather than a scan).

In my testing there has been no reduction in performance by using a parser that supports wildcards for all glob strings.

* Make globs of "/" work with globbing code (previously these were handled by non-globbing code).
* Make globs of "/" match everything (equivalent to "/**").
* Move to using single (globing, glob_get_normal_sf) function for glob strings with and without globbing characters. No performance impact.
* Removed unused non-globbing code (_glob_get_filename_sf and _glob_get_tuple_sf).
* Add more scan tests.

Minor ancillary change that were made:
* Removed run-tests-ve, which was identical to run-tests.

There is substantial refactoring that I plan to do to both the testing and substantive code, but I will do that as a separate branch to keep things tidy.

To post a comment you must log in.
Revision history for this message
Kenneth Loafman (kenneth-loafman) wrote :

Looking good, thanks! If there are any changes needed in the man page, it's important we get those out as well. My understanding is that there should be none, just wanted to check.

Revision history for this message
Kenneth Loafman (kenneth-loafman) wrote :

> Looking good, thanks! If there are any changes needed in the man page, it's
> important we get those out as well. My understanding is that there should be
> none, just wanted to check.

My bad. I did not see the duplicity.1 entry. Sorry.

Revision history for this message
Aaron Whitehouse (aaron-whitehouse) wrote :

Thanks Kenneth,

No, there should be no changes to the man page, as there should be no change to behaviour from how the functions should work. In practice I have not found any situations where the functional behaviour changed.

There are minor changes to responses to individual functions in certain cases, as these were returning different values depending on whether the glob string had a wildcard or not. In these cases, my view was that the non-wildcard responses were less correct than the wildcard responses, but it will be rare that these would have a real-world impact, e.g.:
- the select function returning "None" (silent on the file) vs 0 (do not include the file) for something included in an exclude line; or
- the selection function returning "1" (always include) vs "2" (include only if contents are marked for inclusion).

I mention all of this as there is some risk of changes to functional behaviour (which is why I waited until the 0.8 series), but there is no intended change to what it should be doing. Potentially it will fix some unexpected glob parsing behaviour.

Revision history for this message
Kenneth Loafman (kenneth-loafman) wrote :

A lot of the code was written with 0 for False and 1 for True originally.
I can't remember when the constants were added to Python, but it was
written after that. Not saying we should change it, just making an
observation.

On Tue, Jan 31, 2017 at 7:44 AM, Aaron Whitehouse <email address hidden>
wrote:

> Thanks Kenneth,
>
> No, there should be no changes to the man page, as there should be no
> change to behaviour from how the functions should work. In practice I have
> not found any situations where the functional behaviour changed.
>
> There are minor changes to responses to individual functions in certain
> cases, as these were returning different values depending on whether the
> glob string had a wildcard or not. In these cases, my view was that the
> non-wildcard responses were less correct than the wildcard responses, but
> it will be rare that these would have a real-world impact, e.g.:
> - the select function returning "None" (silent on the file) vs 0 (do not
> include the file) for something included in an exclude line; or
> - the selection function returning "1" (always include) vs "2" (include
> only if contents are marked for inclusion).
>
> I mention all of this as there is some risk of changes to functional
> behaviour (which is why I waited until the 0.8 series), but there is no
> intended change to what it should be doing. Potentially it will fix some
> unexpected glob parsing behaviour.
> --
> https://code.launchpad.net/~aaron-whitehouse/duplicity/08-
> merge-glob-parsers/+merge/315851
> Your team duplicity-team is requested to review the proposed merge of
> lp:~aaron-whitehouse/duplicity/08-merge-glob-parsers into lp:duplicity.
>
> _______________________________________________
> Mailing list: https://launchpad.net/~duplicity-team
> Post to : <email address hidden>
> Unsubscribe : https://launchpad.net/~duplicity-team
> More help : https://help.launchpad.net/ListHelp
>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'duplicity/globmatch.py'
2--- duplicity/globmatch.py 2016-12-11 16:53:12 +0000
3+++ duplicity/globmatch.py 2017-01-29 21:31:37 +0000
4@@ -59,10 +59,15 @@
5 1 - if the file should be included
6 2 - if the folder should be scanned for any included/excluded files
7 None - if the selection function has nothing to say about the file
8+
9+ Note: including a folder implicitly includes everything within it.
10 """
11 glob_ends_w_slash = False
12
13- if glob_str != "/" and glob_str[-1] == "/":
14+ if glob_str == "/":
15+ # If the glob string is '/', it implicitly includes everything
16+ glob_str = "/**"
17+ elif glob_str[-1] == "/":
18 glob_ends_w_slash = True
19 # Remove trailing / from directory name (unless that is the entire
20 # string)
21@@ -80,7 +85,8 @@
22 # string translated into regex
23 # ($|/) nothing must follow except for the end of the string, newline or /
24 # Note that the "/" at the end of the regex means that it will match
25- # if the glob matches a parent folders of path
26+ # if the glob matches a parent folders of path, i.e. including a folder
27+ # includes everything within it.
28 glob_comp_re = re_comp("^%s($|/)" % glob_to_regex(glob_str))
29
30 if glob_ends_w_slash:
31@@ -101,6 +107,11 @@
32 "|".join(_glob_get_prefix_regexs(glob_str)))
33
34 def test_fn(path):
35+ assert not path.name[-1] == "/" or path.name == "/", \
36+ "path.name should never end in '/' during normal operation for " \
37+ "normal paths (except '/' alone)\n" \
38+ "path.name here is " + path.name + " and glob is " + glob_str
39+
40 if glob_comp_re.match(path.name):
41 # Path matches glob, or is contained within a matching folder
42 if not glob_ends_w_slash:
43
44=== modified file 'duplicity/selection.py'
45--- duplicity/selection.py 2016-12-26 21:15:37 +0000
46+++ duplicity/selection.py 2017-01-29 21:31:37 +0000
47@@ -429,9 +429,6 @@
48 assert include == 0 or include == 1
49 if glob_str == "**":
50 sel_func = lambda path: include
51- elif not self.glob_re.match(glob_str):
52- # normal file
53- sel_func = self.glob_get_filename_sf(glob_str, include)
54 else:
55 sel_func = self.glob_get_normal_sf(glob_str, include)
56
57@@ -477,66 +474,6 @@
58 (include and "include-if-present" or "exclude-if-present", filename)
59 return sel_func
60
61- def glob_get_filename_sf(self, filename, include):
62- """Get a selection function given a normal filename
63-
64- Some of the parsing is better explained in
65- filelist_parse_line. The reason this is split from normal
66- globbing is things are a lot less complicated if no special
67- globbing characters are used.
68-
69- """
70- # Internal. Used by glob_get_sf and unit tests.
71- # ToDo: Make all globbing/non-globbing use same code path
72- # This distinction has bitten us too many times with bugs in one or
73- # the other.
74- match_only_dirs = False
75-
76- if filename != "/" and filename[-1] == "/":
77- match_only_dirs = True
78- # Remove trailing / from directory name (unless that is the entire
79- # string)
80- filename = filename[:-1]
81-
82- if not filename.startswith(self.prefix):
83- raise FilePrefixError(filename)
84- index = tuple(filter(lambda x: x,
85- filename[len(self.prefix):].split("/")))
86- return self.glob_get_tuple_sf(index, include, match_only_dirs)
87-
88- def glob_get_tuple_sf(self, tuple, include, match_only_dirs=False):
89- """Return selection function based on tuple"""
90- # Internal. Used by glob_get_filename_sf.
91-
92- def include_sel_func(path):
93- if len(tuple) == len(path.index) and match_only_dirs and not path.isdir():
94- # If we are assessing the actual directory (rather than the
95- # contents of the directory) and the glob ended with a /,
96- # only match directories
97- return None
98- elif (path.index == tuple[:len(path.index)] or
99- path.index[:len(tuple)] == tuple):
100- return 1 # /foo/bar implicitly matches /foo, vice-versa
101- else:
102- return None
103-
104- def exclude_sel_func(path):
105- if match_only_dirs and not path.isdir():
106- # If the glob ended with a /, only match directories
107- return None
108- elif path.index[:len(tuple)] == tuple:
109- return 0 # /foo excludes /foo/bar, not vice-versa
110- else:
111- return None
112-
113- if include == 1:
114- sel_func = include_sel_func
115- elif include == 0:
116- sel_func = exclude_sel_func
117- sel_func.exclude = not include
118- sel_func.name = "Tuple select %s" % (tuple,)
119- return sel_func
120-
121 def glob_get_normal_sf(self, glob_str, include):
122 """Return selection function based on glob_str
123
124@@ -562,9 +499,13 @@
125 glob_str = glob_str[len("ignorecase:"):].lower()
126 ignore_case = True
127
128- # Check to make sure prefix is ok
129- if not path_matches_glob_fn(glob_str, include=1)(self.rootpath):
130- raise FilePrefixError(glob_str)
131+ # Check to make sure prefix is ok, i.e. the glob string is within
132+ # the root folder being backed up
133+ file_prefix_selection = path_matches_glob_fn(glob_str, include=1)(self.rootpath)
134+ if not file_prefix_selection:
135+ # file_prefix_selection == 1 (include) or 2 (scan)
136+ raise FilePrefixError(glob_str + " glob with " + self.rootpath.name +
137+ " path gives " + str(file_prefix_selection))
138
139 return path_matches_glob_fn(glob_str, include, ignore_case)
140
141
142=== modified file 'testing/functional/test_selection.py'
143--- testing/functional/test_selection.py 2017-01-15 18:59:42 +0000
144+++ testing/functional/test_selection.py 2017-01-29 21:31:37 +0000
145@@ -1088,5 +1088,36 @@
146 restored = self.directory_tree_to_list_of_lists(restore_dir)
147 self.assertEqual(restored, [])
148
149+
150+class TestAbsolutePaths(IncludeExcludeFunctionalTest):
151+ """ Tests include/exclude options with absolute paths"""
152+
153+ def test_absolute_paths_non_globbing(self):
154+ """ Test --include and --exclude work with absolute paths"""
155+ self.backup("full", os.path.abspath("testfiles/select2"),
156+ options=["--include", os.path.abspath("testfiles/select2/3/3sub3/3sub3sub2/3sub3sub2_file.txt"),
157+ "--exclude", os.path.abspath("testfiles/select2/3/3sub3/3sub3sub2"),
158+ "--include", os.path.abspath("testfiles/select2/3/3sub2/3sub2sub2"),
159+ "--include", os.path.abspath("testfiles/select2/3/3sub3"),
160+ "--exclude", os.path.abspath("testfiles/select2/3/3sub1"),
161+ "--exclude", os.path.abspath("testfiles/select2/2/2sub1/2sub1sub3"),
162+ "--exclude", os.path.abspath("testfiles/select2/2/2sub1/2sub1sub2"),
163+ "--include", os.path.abspath("testfiles/select2/2/2sub1"),
164+ "--exclude", os.path.abspath("testfiles/select2/1/1sub3/1sub3sub2"),
165+ "--exclude", os.path.abspath("testfiles/select2/1/1sub3/1sub3sub1"),
166+ "--exclude", os.path.abspath("testfiles/select2/1/1sub2/1sub2sub3"),
167+ "--include", os.path.abspath("testfiles/select2/1/1sub2/1sub2sub1"),
168+ "--exclude", os.path.abspath("testfiles/select2/1/1sub1/1sub1sub3/1sub1sub3_file.txt"),
169+ "--exclude", os.path.abspath("testfiles/select2/1/1sub1/1sub1sub2"),
170+ "--exclude", os.path.abspath("testfiles/select2/1/1sub2"),
171+ "--include", os.path.abspath("testfiles/select2/1.py"),
172+ "--include", os.path.abspath("testfiles/select2/3"),
173+ "--include", os.path.abspath("testfiles/select2/1"),
174+ "--exclude", os.path.abspath("testfiles/select2/**")])
175+ self.restore()
176+ restore_dir = 'testfiles/restore_out'
177+ restored = self.directory_tree_to_list_of_lists(restore_dir)
178+ self.assertEqual(restored, self.expected_restored_tree)
179+
180 if __name__ == "__main__":
181 unittest.main()
182
183=== removed file 'testing/run-tests-ve'
184--- testing/run-tests-ve 2015-07-31 14:49:46 +0000
185+++ testing/run-tests-ve 1970-01-01 00:00:00 +0000
186@@ -1,23 +0,0 @@
187-#!/bin/bash
188-#
189-# Copyright 2002 Ben Escoto <ben@emerose.org>
190-# Copyright 2007 Kenneth Loafman <kenneth@loafman.com>
191-# Copyright 2011 Canonical Ltd
192-#
193-# This file is part of duplicity.
194-#
195-# Duplicity is free software; you can redistribute it and/or modify it
196-# under the terms of the GNU General Public License as published by the
197-# Free Software Foundation; either version 2 of the License, or (at your
198-# option) any later version.
199-#
200-# Duplicity is distributed in the hope that it will be useful, but
201-# WITHOUT ANY WARRANTY; without even the implied warranty of
202-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
203-# General Public License for more details.
204-#
205-# You should have received a copy of the GNU General Public License
206-# along with duplicity; if not, write to the Free Software Foundation,
207-# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
208-
209-tox
210
211=== modified file 'testing/unit/test_globmatch.py'
212--- testing/unit/test_globmatch.py 2016-12-11 16:53:12 +0000
213+++ testing/unit/test_globmatch.py 2017-01-29 21:31:37 +0000
214@@ -44,6 +44,25 @@
215 assert r == "[a*b-c]e[^]]", r
216
217
218+class TestMiscellaneousGlobMatching(UnitTestCase):
219+ """Test various glob matching"""
220+
221+ def test_glob_scans_parent_directories(self):
222+ """Test glob scans parent"""
223+ with patch('duplicity.path.Path.isdir') as mock_isdir:
224+ mock_isdir.return_value = True
225+ # Note: paths ending in '/' do not work!
226+ # self.assertEqual(
227+ # path_matches_glob_fn("testfiles/parent/sub/", 1)(Path(
228+ # "testfiles/parent/")), 2)
229+ self.assertEqual(
230+ path_matches_glob_fn("testfiles/parent/sub", 1)(Path(
231+ "testfiles/parent")), 2)
232+ self.assertEqual(
233+ path_matches_glob_fn("testfiles/select2/3/3sub2", 1)(Path(
234+ "testfiles/select2/3")), 2)
235+
236+
237 class TestDoubleAsteriskOnIncludesExcludes(UnitTestCase):
238 """Test ** on includes and exclude patterns"""
239
240@@ -163,29 +182,26 @@
241 mock_isdir.return_value = False
242 self.assertEqual(
243 path_matches_glob_fn("fold*/", 1)(Path("folder/file.txt")), 1)
244- #
245- # def test_slash_matches_everything(self):
246- # """Test / matches everything"""
247- # # ToDo: Not relevant at this stage, as "/" would not go through
248- # # globmatch because it has no special characters, but it should be
249- # # made to work
250- # with patch('duplicity.path.Path.isdir') as mock_isdir:
251- # mock_isdir.return_value = True
252- # self.assertEqual(
253- # path_matches_glob_fn("/",
254- # 1)(Path("/tmp/testfiles/select/1/2")), 1)
255- # self.assertEqual(
256- # path_matches_glob_fn("/",
257- # 1)(Path("/test/random/path")), 1)
258- # self.assertEqual(
259- # path_matches_glob_fn("/",
260- # 1)(Path("/")), 1)
261- # self.assertEqual(
262- # path_matches_glob_fn("/",
263- # 1)(Path("/var/log")), 1)
264- # self.assertEqual(
265- # path_matches_glob_fn("/",
266- # 1)(Path("/var/log/log.txt")), 1)
267+
268+ def test_slash_matches_everything(self):
269+ """Test / matches everything"""
270+ with patch('duplicity.path.Path.isdir') as mock_isdir:
271+ mock_isdir.return_value = True
272+ self.assertEqual(
273+ path_matches_glob_fn("/",
274+ 1)(Path("/tmp/testfiles/select/1/2")), 1)
275+ self.assertEqual(
276+ path_matches_glob_fn("/",
277+ 1)(Path("/test/random/path")), 1)
278+ self.assertEqual(
279+ path_matches_glob_fn("/",
280+ 1)(Path("/")), 1)
281+ self.assertEqual(
282+ path_matches_glob_fn("/",
283+ 1)(Path("/var/log")), 1)
284+ self.assertEqual(
285+ path_matches_glob_fn("/",
286+ 1)(Path("/var/log/log.txt")), 1)
287
288 def test_slash_star_scans_folder(self):
289 """Test that folder/* scans folder/"""
290
291=== modified file 'testing/unit/test_selection.py'
292--- testing/unit/test_selection.py 2016-12-11 16:53:12 +0000
293+++ testing/unit/test_selection.py 2017-01-29 21:31:37 +0000
294@@ -67,12 +67,12 @@
295 # with patch('duplicity.path.ROPath.isdir', return_value=True):
296 # as build system's mock is too old to support it.
297
298- assert sf2(self.makeext("usr")) == 1
299- assert sf2(self.makeext("usr/local")) == 1
300- assert sf2(self.makeext("usr/local/bin")) == 1
301- assert sf2(self.makeext("usr/local/doc")) is None
302- assert sf2(self.makeext("usr/local/bin/gzip")) == 1
303- assert sf2(self.makeext("usr/local/bingzip")) is None
304+ self.assertEqual(sf2(self.makeext("usr")), 2)
305+ self.assertEqual(sf2(self.makeext("usr/local")), 2)
306+ self.assertEqual(sf2(self.makeext("usr/local/bin")), 1)
307+ self.assertEqual(sf2(self.makeext("usr/local/doc")), None)
308+ self.assertEqual(sf2(self.makeext("usr/local/bin/gzip")), 1)
309+ self.assertEqual(sf2(self.makeext("usr/local/bingzip")), None)
310
311 def test_tuple_exclude(self):
312 """Test exclude selection function made from a regular filename"""
313@@ -138,13 +138,11 @@
314 assert select.glob_get_sf("**.py", 1)(Path("/")) == 2
315 assert select.glob_get_sf("**.py", 1)(Path("foo")) == 2
316 assert select.glob_get_sf("**.py", 1)(Path("usr/local/bin")) == 2
317- assert select.glob_get_sf("/testfiles/select/**.py", 1)(Path("/testfiles/select/")) == 2
318- assert select.glob_get_sf("/testfiles/select/test.py", 1)(Path("/testfiles/select/")) == 1
319- assert select.glob_get_sf("/testfiles/select/test.py", 0)(Path("/testfiles/select/")) is None
320- # assert select.glob_get_normal_sf("/testfiles/se?ect/test.py", 1)(Path("/testfiles/select/")) is None
321- # ToDo: Not sure that the above is sensible behaviour (at least that it differs from a non-globbing
322- # include)
323- assert select.glob_get_normal_sf("/testfiles/select/test.py", 0)(Path("/testfiles/select/")) is None
324+ assert select.glob_get_sf("/testfiles/select/**.py", 1)(Path("/testfiles/select")) == 2
325+ assert select.glob_get_sf("/testfiles/select/test.py", 1)(Path("/testfiles/select")) == 2
326+ assert select.glob_get_normal_sf("/testfiles/se?ect/test.py", 1)(Path("/testfiles/select")) == 2
327+ assert select.glob_get_sf("/testfiles/select/test.py", 0)(Path("/testfiles/select")) is None
328+ assert select.glob_get_normal_sf("/testfiles/select/test.py", 0)(Path("/testfiles/select")) is None
329
330 def test_ignore_case(self):
331 """test_ignore_case - try a few expressions with ignorecase:"""
332@@ -160,11 +158,11 @@
333 root = Path("/")
334 select = Select(root)
335
336- assert select.glob_get_sf("/", 1)(root) == 1
337- assert select.glob_get_sf("/foo", 1)(root) == 1
338- assert select.glob_get_sf("/foo/bar", 1)(root) == 1
339- assert select.glob_get_sf("/", 0)(root) == 0
340- assert select.glob_get_sf("/foo", 0)(root) is None
341+ self.assertEqual(select.glob_get_sf("/", 1)(root), 1)
342+ self.assertEqual(select.glob_get_sf("/foo", 1)(root), 2)
343+ self.assertEqual(select.glob_get_sf("/foo/bar", 1)(root), 2)
344+ self.assertEqual(select.glob_get_sf("/", 0)(root), 0)
345+ self.assertEqual(select.glob_get_sf("/foo", 0)(root), None)
346
347 assert select.glob_get_sf("**.py", 1)(root) == 2
348 assert select.glob_get_sf("**", 1)(root) == 1
349@@ -809,7 +807,7 @@
350
351 def test_exclude_after_scan(self):
352 """Test select with an exclude after a pattern that would return a scan for that file"""
353- self.root = Path("testfiles/select2/3/")
354+ self.root = Path("testfiles/select2/3")
355 self.ParseTest([("--include", "testfiles/select2/3/**file.txt"),
356 ("--exclude", "testfiles/select2/3/3sub2"),
357 ("--include", "testfiles/select2/3/3sub1"),
358@@ -892,7 +890,8 @@
359
360 def test_glob_get_normal_sf_exclude_root(self):
361 """Test simple exclude with / as the glob."""
362- self.assertEqual(self.exclude_glob_tester("/.git", "/"), None)
363+ self.assertEqual(self.exclude_glob_tester("/.git", "/"), 0)
364+ self.assertEqual(self.exclude_glob_tester("/testfile", "/"), 0)
365
366 def test_glob_get_normal_sf_2(self):
367 """Test same behaviour as the functional test test_globbing_replacement."""
368@@ -954,7 +953,6 @@
369 def test_glob_get_normal_sf_3_double_asterisks_dirs_to_scan(self):
370 """Test double asterisk (**) replacement in glob_get_normal_sf with directories that should be scanned"""
371 # The new special pattern, **, expands to any string of characters whether or not it contains "/".
372- self.assertEqual(self.include_glob_tester("/long/example/path/", "/**/hello.txt"), 2)
373 self.assertEqual(self.include_glob_tester("/long/example/path", "/**/hello.txt"), 2)
374
375 def test_glob_get_normal_sf_3_ignorecase(self):
376@@ -976,5 +974,55 @@
377 self.assertEqual(self.exclude_glob_tester("TEstFiles/SeLect2/2", "ignorecase:t?stFile**ect2/2",
378 "testfiles/select2"), 0)
379
380+ def test_glob_dirs_to_scan(self):
381+ """Test parent directories are marked as needing to be scanned"""
382+ with patch('duplicity.path.Path.isdir') as mock_isdir:
383+ mock_isdir.return_value = True
384+ self.assertEqual(
385+ self.glob_tester("parent", "parent/hello.txt", 1, "parent"), 2)
386+
387+ def test_glob_dirs_to_scan_glob(self):
388+ """Test parent directories are marked as needing to be scanned - globs"""
389+ with patch('duplicity.path.Path.isdir') as mock_isdir:
390+ mock_isdir.return_value = True
391+ self.assertEqual(
392+ self.glob_tester("testfiles/select/1", "*/select/1/1", 1,
393+ "testfiles/select"), 2)
394+ self.assertEqual(
395+ self.glob_tester("testfiles/select/1/2",
396+ "*/select/1/2/1", 1, "testfiles/select"), 2)
397+ self.assertEqual(
398+ self.glob_tester("parent", "parent/hel?o.txt", 1, "parent"), 2)
399+ self.assertEqual(
400+ self.glob_tester("test/parent/folder",
401+ "test/par*t/folder/hello.txt", 1, "test"), 2)
402+ self.assertEqual(
403+ self.glob_tester("testfiles/select/1/1",
404+ "**/1/2/1", 1, "testfiles"), 2)
405+ self.assertEqual(
406+ self.glob_tester("testfiles/select2/3/3sub2",
407+ "testfiles/select2/3/**file.txt", 1,
408+ "testfiles"), 2)
409+ self.assertEqual(
410+ self.glob_tester("testfiles/select/1/2",
411+ "*/select/1/2/1", 1, "testfiles"), 2)
412+ self.assertEqual(
413+ self.glob_tester("testfiles/select/1",
414+ "testfiles/select**/2", 1, "testfiles"), 2)
415+ self.assertEqual(
416+ self.glob_tester("testfiles/select/efools",
417+ "testfiles/select/*foo*/p*", 1,
418+ "testfiles"), 2)
419+ self.assertEqual(
420+ self.glob_tester("testfiles/select/3",
421+ "testfiles/select/**2", 1, "testfiles"), 2)
422+ self.assertEqual(
423+ self.glob_tester("testfiles/select2/1/1sub1/1sub1sub2",
424+ "testfiles/select2/**/3sub3sub2/3sub3su?2_file.txt",
425+ 1, "testfiles"), 2)
426+ self.assertEqual(
427+ self.glob_tester("testfiles/select/1",
428+ "*/select/1/1", 1, "testfiles"), 2)
429+
430 if __name__ == "__main__":
431 unittest.main()

Subscribers

People subscribed via source and target branches