Status: | Needs review | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~a1s/brz/3.2 | ||||
Merge into: | lp:brz/3.2 | ||||
Diff against target: |
671 lines (+216/-99) 9 files modified
breezy/_walkdirs_win32.pyx (+19/-17) breezy/osutils.py (+2/-1) breezy/plugins/upload/tests/test_upload.py (+13/-0) breezy/tests/__init__.py (+31/-11) breezy/tests/test__walkdirs_win32.py (+13/-16) breezy/tests/test_bedding.py (+28/-5) breezy/tests/test_config.py (+42/-19) breezy/tests/test_osutils.py (+63/-27) breezy/transport/local.py (+5/-3) |
||||
To merge this branch: | bzr merge lp:~a1s/brz/3.2 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Breezy developers | Pending | ||
Review via email: mp+412781@code.launchpad.net |
Commit message
Description of the change
This is an ongoing effort to make Breezy work on Windows
Jelmer Vernooij (jelmer) : | # |
Aleksandr Smyshliaev (a1s) : | # |
Jelmer Vernooij (jelmer) : | # |
Aleksandr Smyshliaev (a1s) : | # |
- 7564. By Aleksandr Smyshliaev
-
Merge brz/3.2
- 7565. By Aleksandr Smyshliaev
-
Get rid of osutils.open_file: in Python 3.4 and newer file descriptors are non-inheritable by default
- 7566. By Aleksandr Smyshliaev
-
Use relative import paths in test__walkdirs_
win32
Aleksandr Smyshliaev (a1s) : | # |
Jelmer Vernooij (jelmer) : | # |
- 7567. By Aleksandr Smyshliaev
-
Fix issues discussed in https:/
/code.launchpad .net/~a1s/ brz/3.2/ +merge/ 412781 - 7568. By Aleksandr Smyshliaev
-
Merge lp:brz/3.2
Aleksandr Smyshliaev (a1s) wrote : | # |
Revision 7568 is not ready to be merged, sorry. It went OK on Windows, but then I found that it's broken on *nix. I'm going to fix it and make another commit in a day or two.
- 7569. By Aleksandr Smyshliaev
-
Add win32utils.
list_fs_ types() - 7570. By Aleksandr Smyshliaev
-
Fix regression on *nix
Aleksandr Smyshliaev (a1s) wrote : | # |
Ok, now this is tested on Linux/Python 3.10. Both test_config and test_osutils run without errors.
On Windows, test_config runs without errors, and test_osutils has two failures in test_osutils.
I've replied to all questions asked here in diff comments.
Please check if this can be merged now.
Jelmer Vernooij (jelmer) wrote : | # |
It would still be useful to split this merge proposal into several separate ones, e.g. one with the filesystem type changes, one with the symlink changes, one with the config changes, one with the walkdir fixes, etc.
Aleksandr Smyshliaev (a1s) wrote : | # |
> It would still be useful to split this merge proposal into several separate
> ones, e.g. one with the filesystem type changes, one with the symlink changes,
> one with the config changes, one with the walkdir fixes, etc.
I surely see the point in submitting each atomic change in a different merge proposal.
The problem is, I'm not sure the regression would run with just one of those things changed and others left as is. Perhaps I would try that if the merge proposals are reviewed in hours, not weeks.
At the moment, my first priority is getting qbrz work on Linux, and then I'm going to take a pause to handle my RL issues. I'd really like to have Breezy working on Windows, but right now Bazaar 2.6 covers my needs there.
Jelmer Vernooij (jelmer) wrote : | # |
> > It would still be useful to split this merge proposal into several separate
> > ones, e.g. one with the filesystem type changes, one with the symlink
> changes,
> > one with the config changes, one with the walkdir fixes, etc.
>
> I surely see the point in submitting each atomic change in a different merge
> proposal.
>
> The problem is, I'm not sure the regression would run with just one of those
> things changed and others left as is. Perhaps I would try that if the merge
> proposals are reviewed in hours, not weeks.
The delay in reviews is actually a function of the size of the merge proposals; for the smaller ones that are easy to understand the full implications of, I usually just process them as part of my regular email reading but for larger ones I need to schedule some time to sit down and understand them.
Jelmer
Aleksandr Smyshliaev (a1s) wrote : | # |
I do understand. I'm sorry there are too many things to address at once.
- 7571. By Aleksandr Smyshliaev
-
Merge lp:brz/3.2
- 7572. By Aleksandr Smyshliaev
-
Merge lp:brz/3.2
Unmerged revisions
- 7572. By Aleksandr Smyshliaev
-
Merge lp:brz/3.2
- 7571. By Aleksandr Smyshliaev
-
Merge lp:brz/3.2
- 7570. By Aleksandr Smyshliaev
-
Fix regression on *nix
- 7569. By Aleksandr Smyshliaev
-
Add win32utils.
list_fs_ types() - 7568. By Aleksandr Smyshliaev
-
Merge lp:brz/3.2
- 7567. By Aleksandr Smyshliaev
-
Fix issues discussed in https:/
/code.launchpad .net/~a1s/ brz/3.2/ +merge/ 412781 - 7566. By Aleksandr Smyshliaev
-
Use relative import paths in test__walkdirs_
win32 - 7565. By Aleksandr Smyshliaev
-
Get rid of osutils.open_file: in Python 3.4 and newer file descriptors are non-inheritable by default
- 7564. By Aleksandr Smyshliaev
-
Merge brz/3.2
- 7563. By Aleksandr Smyshliaev
-
Fix regression tests run by selftest test_config on Windows
Preview Diff
1 | === modified file 'breezy/_walkdirs_win32.pyx' |
2 | --- breezy/_walkdirs_win32.pyx 2020-06-08 19:42:53 +0000 |
3 | +++ breezy/_walkdirs_win32.pyx 2022-01-18 07:08:36 +0000 |
4 | @@ -58,14 +58,11 @@ |
5 | |
6 | int GetLastError() |
7 | |
8 | - # Wide character functions |
9 | - DWORD wcslen(WCHAR *) |
10 | - |
11 | |
12 | cdef extern from "Python.h": |
13 | - WCHAR *PyUnicode_AS_UNICODE(object) |
14 | - Py_ssize_t PyUnicode_GET_SIZE(object) |
15 | - object PyUnicode_FromUnicode(WCHAR *, Py_ssize_t) |
16 | + WCHAR *PyUnicode_AsWideCharString(object, Py_ssize_t *size) |
17 | + Py_ssize_t PyUnicode_GET_LENGTH(object) |
18 | + object PyUnicode_FromWideChar(WCHAR *, Py_ssize_t) |
19 | int PyList_Append(object, object) except -1 |
20 | object PyUnicode_AsUTF8String(object) |
21 | |
22 | @@ -120,12 +117,6 @@ |
23 | self.st_mtime, self.st_ctime)) |
24 | |
25 | |
26 | -cdef object _get_name(WIN32_FIND_DATAW *data): |
27 | - """Extract the Unicode name for this file/dir.""" |
28 | - return PyUnicode_FromUnicode(data.cFileName, |
29 | - wcslen(data.cFileName)) |
30 | - |
31 | - |
32 | cdef int _get_mode_bits(WIN32_FIND_DATAW *data): # cannot_raise |
33 | cdef int mode_bits |
34 | |
35 | @@ -213,25 +204,36 @@ |
36 | def read_dir(self, prefix, top): |
37 | """Win32 implementation of DirReader.read_dir. |
38 | |
39 | + :param prefix: A utf8 prefix to be preprended to the path basenames. |
40 | + :param top: A Unicode or UTF8-encoded path to read. |
41 | + |
42 | + :return: A list of the directories contents. Each item contains: |
43 | + (utf8_relpath, utf8_name, kind, lstatvalue, unicode_abspath) |
44 | + |
45 | :seealso: DirReader.read_dir |
46 | + |
47 | """ |
48 | cdef WIN32_FIND_DATAW search_data |
49 | cdef HANDLE hFindFile |
50 | cdef int last_err |
51 | cdef WCHAR *query |
52 | cdef int result |
53 | + cdef Py_ssize_t length |
54 | |
55 | + global osutils |
56 | + if osutils is None: |
57 | + from . import osutils |
58 | if prefix: |
59 | - relprefix = prefix + '/' |
60 | + relprefix = osutils.safe_utf8(prefix) + b'/' |
61 | else: |
62 | - relprefix = '' |
63 | - top_slash = top + '/' |
64 | + relprefix = b'' |
65 | + top_slash = osutils.safe_unicode(top) + '/' |
66 | |
67 | top_star = top_slash + '*' |
68 | |
69 | dirblock = [] |
70 | |
71 | - query = PyUnicode_AS_UNICODE(top_star) |
72 | + query = PyUnicode_AsWideCharString(top_star, &length) |
73 | hFindFile = FindFirstFileW(query, &search_data) |
74 | if hFindFile == INVALID_HANDLE_VALUE: |
75 | # Raise an exception? This path doesn't seem to exist |
76 | @@ -244,7 +246,7 @@ |
77 | if _should_skip(&search_data): |
78 | result = FindNextFileW(hFindFile, &search_data) |
79 | continue |
80 | - name_unicode = _get_name(&search_data) |
81 | + name_unicode = PyUnicode_FromWideChar(search_data.cFileName, -1) |
82 | name_utf8 = PyUnicode_AsUTF8String(name_unicode) |
83 | PyList_Append(dirblock, |
84 | (relprefix + name_utf8, name_utf8, |
85 | |
86 | === modified file 'breezy/osutils.py' |
87 | --- breezy/osutils.py 2022-01-14 16:48:37 +0000 |
88 | +++ breezy/osutils.py 2022-01-18 07:08:36 +0000 |
89 | @@ -24,6 +24,7 @@ |
90 | |
91 | from .lazy_import import lazy_import |
92 | lazy_import(globals(), """ |
93 | +import ctypes |
94 | from datetime import datetime |
95 | import getpass |
96 | import locale |
97 | @@ -1301,7 +1302,7 @@ |
98 | # filesystems), for example, so could probably benefit from the same basic |
99 | # support there. For now though, only Windows and OSX get that support, and |
100 | # they get it for *all* file-systems! |
101 | -if sys.platform in ('win32', 'darwin'): |
102 | +if sys.platform in ('win32', 'darwin', 'cygwin'): |
103 | canonical_relpath = _cicp_canonical_relpath |
104 | else: |
105 | canonical_relpath = relpath |
106 | |
107 | === modified file 'breezy/plugins/upload/tests/test_upload.py' |
108 | --- breezy/plugins/upload/tests/test_upload.py 2021-12-08 18:46:11 +0000 |
109 | +++ breezy/plugins/upload/tests/test_upload.py 2022-01-18 07:08:36 +0000 |
110 | @@ -109,6 +109,16 @@ |
111 | self.assertFileEqual(content, osutils.pathjoin(base, path)) |
112 | |
113 | def assertUpPathModeEqual(self, path, expected_mode, base=upload_dir): |
114 | + # FIXME: at present, the upload is tested locally, |
115 | + # so if local FS doesn't support the mode bits, |
116 | + # all mode tests will fail. The only mode bit |
117 | + # that is reported as unsupported by the osutils module |
118 | + # is the executable bit. So skip if not supports_executable. |
119 | + # This should better be tested with a dummy transport |
120 | + # and not an actual file system. |
121 | + if not osutils.supports_executable(path): |
122 | + self.skipTest("Cannot determine mode bits for %s" % path) |
123 | + return |
124 | # FIXME: the tests needing that assertion should depend on the server |
125 | # ability to handle chmod so that they don't fail (or be skipped) |
126 | # against servers that can't. Note that some breezy transports define |
127 | @@ -387,6 +397,9 @@ |
128 | self.assertUpFileEqual(b'bar', 'hello') |
129 | |
130 | def _test_make_file_executable(self, file_name): |
131 | + if not osutils.supports_executable(file_name) |
132 | + self.skipTest("%s cannot be marked executable" % filename) |
133 | + return |
134 | self.make_branch_and_working_tree() |
135 | self.add_file(file_name, b'foo') |
136 | self.chmod_file(file_name, 0o664) |
137 | |
138 | === modified file 'breezy/tests/__init__.py' |
139 | --- breezy/tests/__init__.py 2022-01-09 13:12:27 +0000 |
140 | +++ breezy/tests/__init__.py 2022-01-18 07:08:36 +0000 |
141 | @@ -2654,15 +2654,9 @@ |
142 | """ |
143 | root = TestCaseWithMemoryTransport.TEST_ROOT |
144 | try: |
145 | - # Make sure we get a readable and accessible home for brz.log |
146 | - # and/or config files, and not fallback to weird defaults (see |
147 | - # http://pad.lv/825027). |
148 | - self.assertIs(None, os.environ.get('BRZ_HOME', None)) |
149 | - os.environ['BRZ_HOME'] = root |
150 | from breezy.bzr.bzrdir import BzrDirMetaFormat1 |
151 | wt = controldir.ControlDir.create_standalone_workingtree( |
152 | root, format=BzrDirMetaFormat1()) |
153 | - del os.environ['BRZ_HOME'] |
154 | except Exception as e: |
155 | self.fail("Fail to initialize the safety net: %r\n" % (e,)) |
156 | # Hack for speed: remember the raw bytes of the dirstate file so that |
157 | @@ -2696,11 +2690,31 @@ |
158 | suffix='.tmp')) |
159 | TestCaseWithMemoryTransport.TEST_ROOT = root |
160 | |
161 | + # Set BRZ_HOME to the test directory so that we never interfere |
162 | + # with any configuration files existing on the system. |
163 | + # |
164 | + # Note that we need both BRZ_HOME AND BZR_HOME: |
165 | + # BRZ_HOME may be set in the environment and we have to replace it, |
166 | + # but then bedding doesn't find breezy config in the temporary |
167 | + # directory, and falls back to BZR_HOME. |
168 | + # |
169 | + # This needs to be done before ._create_safety_net is called |
170 | + # so that the working tree created there doesn't mess with |
171 | + # the system environment. |
172 | + self.overrideEnv('BRZ_HOME', root) |
173 | + self.overrideEnv('BZR_HOME', root) |
174 | + |
175 | self._create_safety_net() |
176 | |
177 | # The same directory is used by all tests, and we're not |
178 | # specifically told when all tests are finished. This will do. |
179 | atexit.register(_rmtree_temp_dir, root) |
180 | + else: |
181 | + # Re-using existing safety net directory; |
182 | + # isolate the environment (see comment above) |
183 | + root = TestCaseWithMemoryTransport.TEST_ROOT |
184 | + self.overrideEnv('BRZ_HOME', root) |
185 | + self.overrideEnv('BZR_HOME', root) |
186 | |
187 | self.permit_dir(TestCaseWithMemoryTransport.TEST_ROOT) |
188 | self.addCleanup(self._check_safety_net) |
189 | @@ -2848,7 +2862,15 @@ |
190 | |
191 | def check_file_contents(self, filename, expect): |
192 | self.log("check contents of file %s" % filename) |
193 | - with open(filename, 'rb') as f: |
194 | + # This seems to be called for text files only, |
195 | + # but most of the existing tests check the contents |
196 | + # against binary strings. Now there are cases |
197 | + # when we need to normalize the line endings; expect a string then. |
198 | + if isinstance(expect, str): |
199 | + args = {"mode": "rt", "encoding": "utf-8"} |
200 | + else: |
201 | + args = {"mode": "rb"} |
202 | + with open(filename, **args) as f: |
203 | contents = f.read() |
204 | if contents != expect: |
205 | self.log("expected: %r" % expect) |
206 | @@ -4467,11 +4489,9 @@ |
207 | if test_id is not None: |
208 | ui.ui_factory.clear_term() |
209 | sys.stderr.write('\nWhile running: %s\n' % (test_id,)) |
210 | - # Ugly, but the last thing we want here is fail, so bear with it. |
211 | - printable_e = str(e).decode(osutils.get_user_encoding(), 'replace' |
212 | - ).encode('ascii', 'replace') |
213 | + # 21-nov-2021 [a1s] Revert revision 5231: in Python3, str has no decode |
214 | sys.stderr.write('Unable to remove testing dir %s\n%s' |
215 | - % (os.path.basename(dirname), printable_e)) |
216 | + % (os.path.basename(dirname), e)) |
217 | |
218 | |
219 | def probe_unicode_in_user_encoding(): |
220 | |
221 | === modified file 'breezy/tests/test__walkdirs_win32.py' |
222 | --- breezy/tests/test__walkdirs_win32.py 2018-11-12 01:41:38 +0000 |
223 | +++ breezy/tests/test__walkdirs_win32.py 2022-01-18 07:08:36 +0000 |
224 | @@ -37,9 +37,7 @@ |
225 | |
226 | def setUp(self): |
227 | super(TestWin32Finder, self).setUp() |
228 | - from ._walkdirs_win32 import ( |
229 | - Win32ReadDir, |
230 | - ) |
231 | + from .._walkdirs_win32 import Win32ReadDir |
232 | self.reader = Win32ReadDir() |
233 | |
234 | def _remove_stat_from_dirblock(self, dirblock): |
235 | @@ -58,45 +56,44 @@ |
236 | finally: |
237 | osutils._selected_dir_reader = old_selected_dir_reader |
238 | |
239 | - def assertReadDir(self, expected, prefix, top_unicode): |
240 | + def assertReadDir(self, expected, prefix, top): |
241 | result = self._remove_stat_from_dirblock( |
242 | - self.reader.read_dir(prefix, top_unicode)) |
243 | + self.reader.read_dir(prefix, top)) |
244 | self.assertEqual(expected, result) |
245 | |
246 | def test_top_prefix_to_starting_dir(self): |
247 | # preparing an iteration should create a unicode native path. |
248 | self.assertEqual( |
249 | - ('prefix', None, None, None, u'\x12'), |
250 | + (b'prefix', None, None, None, u'\x12'), |
251 | self.reader.top_prefix_to_starting_dir( |
252 | u'\x12'.encode('utf8'), 'prefix')) |
253 | |
254 | def test_empty_directory(self): |
255 | - self.assertReadDir([], 'prefix', u'.') |
256 | - self.assertWalkdirs([(('', u'.'), [])], u'.') |
257 | + self.assertReadDir([], b'prefix', b'.') |
258 | + self.assertWalkdirs([((b'', u'.'), [])], b'.') |
259 | |
260 | def test_file(self): |
261 | self.build_tree(['foo']) |
262 | - self.assertReadDir([('foo', 'foo', 'file', u'./foo')], |
263 | + self.assertReadDir([(b'foo', b'foo', 'file', u'./foo')], |
264 | '', u'.') |
265 | |
266 | def test_directory(self): |
267 | self.build_tree(['bar/']) |
268 | - self.assertReadDir([('bar', 'bar', 'directory', u'./bar')], |
269 | + self.assertReadDir([(b'bar', b'bar', 'directory', u'./bar')], |
270 | '', u'.') |
271 | |
272 | def test_prefix(self): |
273 | self.build_tree(['bar/', 'baf']) |
274 | self.assertReadDir([ |
275 | - ('xxx/baf', 'baf', 'file', u'./baf'), |
276 | - ('xxx/bar', 'bar', 'directory', u'./bar'), |
277 | + (b'xxx/baf', b'baf', 'file', u'./baf'), |
278 | + (b'xxx/bar', b'bar', 'directory', u'./bar'), |
279 | ], |
280 | 'xxx', u'.') |
281 | |
282 | def test_missing_dir(self): |
283 | e = self.assertRaises(WindowsError, |
284 | - self.reader.read_dir, 'prefix', u'no_such_dir') |
285 | - self.assertEqual(errno.ENOENT, e.errno) |
286 | - self.assertEqual(3, e.winerror) |
287 | + self.reader.read_dir, b'prefix', u'no_such_dir') |
288 | + # 3 is ERROR_PATH_NOT_FOUND, see WinError.h |
289 | self.assertEqual((3, u'no_such_dir/*'), e.args) |
290 | |
291 | |
292 | @@ -106,7 +103,7 @@ |
293 | |
294 | def setUp(self): |
295 | super(Test_Win32Stat, self).setUp() |
296 | - from ._walkdirs_win32 import lstat |
297 | + from .._walkdirs_win32 import lstat |
298 | self.win32_lstat = lstat |
299 | |
300 | def test_zero_members_present(self): |
301 | |
302 | === modified file 'breezy/tests/test_bedding.py' |
303 | --- breezy/tests/test_bedding.py 2022-01-14 00:23:44 +0000 |
304 | +++ breezy/tests/test_bedding.py 2022-01-18 07:08:36 +0000 |
305 | @@ -43,11 +43,17 @@ |
306 | self.overrideEnv('HOME', '/home/bogus') |
307 | self.overrideEnv('XDG_CACHE_HOME', '') |
308 | if sys.platform == 'win32': |
309 | - self.overrideEnv( |
310 | - 'BRZ_HOME', |
311 | - r'C:\Documents and Settings\bogus\Application Data') |
312 | - self.brz_home = \ |
313 | - 'C:/Documents and Settings/bogus/Application Data/breezy' |
314 | + # On Windows, the search sequence is: |
315 | + # 1. BRZ_HOME if breezy config exists or else |
316 | + # if there's no existing bazaar config. |
317 | + # 2. AppData\Roaming (from special shell folders if possible; |
318 | + # if not, then from APPDATA environment variable.) |
319 | + # The HOME environment variable (set above) is never checked. |
320 | + # Let's take BRZ_HOME, as it is the first. |
321 | + # (Also disable BZR_HOME to not find existing bazaar config.) |
322 | + self.overrideEnv('BRZ_HOME', r'C:\Users\bogus\AppData') |
323 | + self.overrideEnv('BZR_HOME', r'C:\Users\bogus\AppData') |
324 | + self.brz_home = 'C:/Users/bogus/AppData/breezy' |
325 | else: |
326 | self.brz_home = '/home/bogus/.config/breezy' |
327 | |
328 | @@ -73,11 +79,19 @@ |
329 | class TestConfigPathFallback(tests.TestCaseInTempDir): |
330 | |
331 | def setUp(self): |
332 | + if sys.platform == 'win32': |
333 | + raise tests.TestNotApplicable( |
334 | + 'Fallback config paths are different on this platform') |
335 | super(TestConfigPathFallback, self).setUp() |
336 | self.overrideEnv('HOME', self.test_dir) |
337 | self.overrideEnv('XDG_CACHE_HOME', '') |
338 | self.bzr_home = os.path.join(self.test_dir, '.bazaar') |
339 | os.mkdir(self.bzr_home) |
340 | + # The safety net made by super() has set BZR_HOME and BRZ_HOME to the |
341 | + # temporary root shared by all tests. As they take precedence, we need |
342 | + # to erase the variables in order to check the HOME created here. |
343 | + self.overrideEnv('BRZ_HOME', None) |
344 | + self.overrideEnv('BZR_HOME', None) |
345 | |
346 | def test_config_dir(self): |
347 | self.assertEqual(bedding.config_dir(), self.bzr_home) |
348 | @@ -192,6 +206,15 @@ |
349 | class TestDefaultMailDomain(tests.TestCaseInTempDir): |
350 | """Test retrieving default domain from mailname file""" |
351 | |
352 | + def setUp(self): |
353 | + if sys.platform == 'win32': |
354 | + # The function deliberately returns None on Windows. |
355 | + # Could run C:\Windows\System32\whoami.exe /upn, but that |
356 | + # only works when the user is logged in to an AD domain. |
357 | + # Why we don't just read /etc/maildomain, same as on all platforms? |
358 | + raise tests.TestNotApplicable |
359 | + super(TestDefaultMailDomain, self).setUp() |
360 | + |
361 | def test_default_mail_domain_simple(self): |
362 | with open('simple', 'w') as f: |
363 | f.write("domainname.com\n") |
364 | |
365 | === modified file 'breezy/tests/test_config.py' |
366 | --- breezy/tests/test_config.py 2020-06-23 01:02:30 +0000 |
367 | +++ breezy/tests/test_config.py 2022-01-18 07:08:36 +0000 |
368 | @@ -1012,12 +1012,12 @@ |
369 | """Creating a new entry in config uses a local path.""" |
370 | branch = self.make_branch('branch', format='knit') |
371 | branch.set_push_location('http://foobar') |
372 | - local_path = osutils.getcwd().encode('utf8') |
373 | + local_path = osutils.getcwd() |
374 | # Surprisingly ConfigObj doesn't create a trailing newline |
375 | self.check_file_contents(bedding.locations_config_path(), |
376 | - b'[%s/branch]\n' |
377 | - b'push_location = http://foobar\n' |
378 | - b'push_location:policy = norecurse\n' |
379 | + '[%s/branch]\n' |
380 | + 'push_location = http://foobar\n' |
381 | + 'push_location:policy = norecurse\n' |
382 | % (local_path,)) |
383 | |
384 | def test_autonick_urlencoded(self): |
385 | @@ -3190,11 +3190,22 @@ |
386 | |
387 | def test_branch_name_basename(self): |
388 | store = self.get_store(self) |
389 | - store._load_from_string(dedent("""\ |
390 | - [/] |
391 | - push_location=my{branchname} |
392 | - """).encode('ascii')) |
393 | - matcher = config.LocationMatcher(store, 'file:///parent/example%3c') |
394 | + if sys.platform == "win32": |
395 | + # Win32 file urls start with file:///x:/, |
396 | + # where x is a valid drive letter |
397 | + store._load_from_string(dedent("""\ |
398 | + [C:/] |
399 | + push_location=my{branchname} |
400 | + """).encode('ascii')) |
401 | + matcher = config.LocationMatcher(store, |
402 | + 'file:///c:/parent/example%3c') |
403 | + else: |
404 | + store._load_from_string(dedent("""\ |
405 | + [/] |
406 | + push_location=my{branchname} |
407 | + """).encode('ascii')) |
408 | + matcher = config.LocationMatcher(store, |
409 | + 'file:///parent/example%3c') |
410 | self.assertEqual('example<', matcher.branch_name) |
411 | ((_, section),) = matcher.get_sections() |
412 | self.assertEqual('example<', section.locals['branchname']) |
413 | @@ -3220,19 +3231,31 @@ |
414 | |
415 | def test_url_vs_local_paths(self): |
416 | # The matcher location is an url and the section names are local paths |
417 | - self.assertSectionIDs(['/foo/bar', '/foo'], |
418 | - 'file:///foo/bar/baz', b'''\ |
419 | -[/foo] |
420 | -[/foo/bar] |
421 | -''') |
422 | + if sys.platform == "win32": |
423 | + # Win32 file urls start with file:///x:/, |
424 | + # where x is a valid drive letter |
425 | + self.assertSectionIDs(['C:/foo/bar', 'C:/foo'], |
426 | + 'file:///c:/foo/bar/baz', |
427 | + b'[C:/foo]\n' |
428 | + b'[C:/foo/bar]\n') |
429 | + else: |
430 | + self.assertSectionIDs(['/foo/bar', '/foo'], |
431 | + 'file:///foo/bar/baz', |
432 | + b'[/foo]\n' |
433 | + b'[/foo/bar]\n') |
434 | |
435 | def test_local_path_vs_url(self): |
436 | # The matcher location is a local path and the section names are urls |
437 | - self.assertSectionIDs(['file:///foo/bar', 'file:///foo'], |
438 | - '/foo/bar/baz', b'''\ |
439 | -[file:///foo] |
440 | -[file:///foo/bar] |
441 | -''') |
442 | + if sys.platform == "win32": |
443 | + self.assertSectionIDs(['file:///C:/foo/bar', 'file:///C:/foo'], |
444 | + 'C:/foo/bar/baz', |
445 | + b'[file:///C:/foo]\n' |
446 | + b'[file:///C:/foo/bar]\n') |
447 | + else: |
448 | + self.assertSectionIDs(['file:///foo/bar', 'file:///foo'], |
449 | + '/foo/bar/baz', |
450 | + b'[file:///foo]\n' |
451 | + b'[file:///foo/bar]\n') |
452 | |
453 | def test_no_name_section_included_when_present(self): |
454 | # Note that other tests will cover the case where the no-name section |
455 | |
456 | === modified file 'breezy/tests/test_osutils.py' |
457 | --- breezy/tests/test_osutils.py 2022-01-16 17:48:26 +0000 |
458 | +++ breezy/tests/test_osutils.py 2022-01-18 07:08:36 +0000 |
459 | @@ -876,8 +876,10 @@ |
460 | self.requireFeature(features.win32_feature) |
461 | self.assertEqual('C:/foo', osutils._win32_abspath('C:\\foo')) |
462 | self.assertEqual('C:/foo', osutils._win32_abspath('C:/foo')) |
463 | - self.assertEqual('//HOST/path', osutils._win32_abspath(r'\\HOST\path')) |
464 | - self.assertEqual('//HOST/path', osutils._win32_abspath('//HOST/path')) |
465 | + self.assertEqual('//HOST/SHARE/path', |
466 | + osutils._win32_abspath(r'\\HOST\share\path')) |
467 | + self.assertEqual('//HOST/SHARE/path', |
468 | + osutils._win32_abspath('//host/SHARE/path')) |
469 | |
470 | def test_realpath(self): |
471 | self.assertEqual('C:/foo', osutils._win32_realpath('C:\\foo')) |
472 | @@ -1068,10 +1070,30 @@ |
473 | |
474 | class TestWalkDirs(tests.TestCaseInTempDir): |
475 | |
476 | + if sys.platform == 'win32': |
477 | + @staticmethod |
478 | + def _normalize_path_separators(path): |
479 | + return path.replace("\\", "/") |
480 | + else: |
481 | + @staticmethod |
482 | + def _normalize_path_separators(path): |
483 | + return path |
484 | + |
485 | def assertExpectedBlocks(self, expected, result): |
486 | - self.assertEqual(expected, |
487 | - [(dirinfo, [line[0:3] for line in block]) |
488 | - for dirinfo, block in result]) |
489 | + # This is randomly called with result obtained from _walkdirs_utf8 |
490 | + # which always uses forward slashes for path separators, |
491 | + # and simple walkdirs, which relies on os.scandir, |
492 | + # so yields paths with backslashes on Windows. |
493 | + # We don't compare the "path-from-top" members here, |
494 | + # but in deeper trees it appears in the "directory-path-from-top". |
495 | + # We have to normalize the slashes in that element. |
496 | + _normalize_walkdirs_result = lambda spec: [( |
497 | + (relpath, self._normalize_path_separators(fromtop)), |
498 | + [line[:3] for line in block], |
499 | + ) for ((relpath, fromtop), block) in spec] |
500 | + self.assertEqual( |
501 | + _normalize_walkdirs_result(expected), |
502 | + _normalize_walkdirs_result(result)) |
503 | |
504 | def test_walkdirs(self): |
505 | tree = [ |
506 | @@ -1223,13 +1245,23 @@ |
507 | self.assertExpectedBlocks(expected_dirblocks[1:], result) |
508 | |
509 | def _filter_out_stat(self, result): |
510 | - """Filter out the stat value from the walkdirs result""" |
511 | - for dirdetail, dirblock in result: |
512 | + """Filter out the stat value from the walkdirs result |
513 | + |
514 | + If needed, also change the directory separators |
515 | + in the path-from-top elements to be forward slashes. |
516 | + |
517 | + """ |
518 | + new_result = [] |
519 | + for ((relpath, fromtop), dirblock) in result: |
520 | new_dirblock = [] |
521 | for info in dirblock: |
522 | # Ignore info[3] which is the stat |
523 | - new_dirblock.append((info[0], info[1], info[2], info[4])) |
524 | - dirblock[:] = new_dirblock |
525 | + new_dirblock.append((info[0], info[1], info[2], |
526 | + self._normalize_path_separators(info[4]))) |
527 | + new_result.append(( |
528 | + (relpath, self._normalize_path_separators(fromtop)), |
529 | + new_dirblock)) |
530 | + return new_result |
531 | |
532 | def _save_platform_info(self): |
533 | self.overrideAttr(osutils, '_fs_enc') |
534 | @@ -1257,6 +1289,8 @@ |
535 | UTF8DirReaderFeature.module.UTF8DirReader, b".") |
536 | |
537 | def test_force_walkdirs_utf8_fs_latin1(self): |
538 | + if sys.platform == "win32": |
539 | + self.skipTest("On Windows, FS functions use wide chars") |
540 | self._save_platform_info() |
541 | osutils._fs_enc = 'iso-8859-1' |
542 | self.assertDirReaderIs(osutils.UnicodeDirReader, ".") |
543 | @@ -1301,11 +1335,9 @@ |
544 | ] |
545 | ), |
546 | ] |
547 | - result = list(osutils.walkdirs('.')) |
548 | - self._filter_out_stat(result) |
549 | + result = self._filter_out_stat(osutils.walkdirs('.')) |
550 | self.assertEqual(expected_dirblocks, result) |
551 | - result = list(osutils.walkdirs(u'./' + name1, name1)) |
552 | - self._filter_out_stat(result) |
553 | + result = self._filter_out_stat(osutils.walkdirs(u'./' + name1, name1)) |
554 | self.assertEqual(expected_dirblocks[1:], result) |
555 | |
556 | def test_unicode__walkdirs_utf8(self): |
557 | @@ -1415,8 +1447,7 @@ |
558 | ] |
559 | ), |
560 | ] |
561 | - result = list(osutils._walkdirs_utf8('.')) |
562 | - self._filter_out_stat(result) |
563 | + result = self._filter_out_stat(osutils._walkdirs_utf8('.')) |
564 | self.assertEqual(expected_dirblocks, result) |
565 | |
566 | def test__walkdirs_utf8_win32readdir(self): |
567 | @@ -1443,26 +1474,25 @@ |
568 | # All of the abspaths should be in unicode, all of the relative paths |
569 | # should be in utf8 |
570 | expected_dirblocks = [ |
571 | - (('', '.'), |
572 | + ((b'', '.'), |
573 | [(name0, name0, 'file', './' + name0u), |
574 | (name1, name1, 'directory', './' + name1u), |
575 | (name2, name2, 'file', './' + name2u), |
576 | ] |
577 | ), |
578 | ((name1, './' + name1u), |
579 | - [(name1 + '/' + name0, name0, 'file', './' + name1u |
580 | + [(name1 + b'/' + name0, name0, 'file', './' + name1u |
581 | + '/' + name0u), |
582 | - (name1 + '/' + name1, name1, 'directory', './' + name1u |
583 | + (name1 + b'/' + name1, name1, 'directory', './' + name1u |
584 | + '/' + name1u), |
585 | ] |
586 | ), |
587 | - ((name1 + '/' + name1, './' + name1u + '/' + name1u), |
588 | + ((name1 + b'/' + name1, './' + name1u + '/' + name1u), |
589 | [ |
590 | ] |
591 | ), |
592 | ] |
593 | - result = list(osutils._walkdirs_utf8(u'.')) |
594 | - self._filter_out_stat(result) |
595 | + result = self._filter_out_stat(osutils._walkdirs_utf8(u'.')) |
596 | self.assertEqual(expected_dirblocks, result) |
597 | |
598 | def assertStatIsCorrect(self, path, win32stat): |
599 | @@ -1471,8 +1501,10 @@ |
600 | self.assertAlmostEqual(os_stat.st_mtime, win32stat.st_mtime, places=4) |
601 | self.assertAlmostEqual(os_stat.st_ctime, win32stat.st_ctime, places=4) |
602 | self.assertAlmostEqual(os_stat.st_atime, win32stat.st_atime, places=4) |
603 | - self.assertEqual(os_stat.st_dev, win32stat.st_dev) |
604 | - self.assertEqual(os_stat.st_ino, win32stat.st_ino) |
605 | + # win32stat is built from WIN32_FIND_DATA structure |
606 | + # which contains neither dwVolumeSerialNumber nor nFileIndex |
607 | + #self.assertEqual(os_stat.st_dev, win32stat.st_dev) |
608 | + #self.assertEqual(os_stat.st_ino, win32stat.st_ino) |
609 | self.assertEqual(os_stat.st_mode, win32stat.st_mode) |
610 | |
611 | def test__walkdirs_utf_win32_find_file_stat_file(self): |
612 | @@ -1489,7 +1521,7 @@ |
613 | with open(name0u, 'ab') as f: |
614 | f.write(b'just a small update') |
615 | |
616 | - result = Win32ReadDir().read_dir('', u'.') |
617 | + result = Win32ReadDir().read_dir(b'', u'.') |
618 | entry = result[0] |
619 | self.assertEqual((name0, name0, 'file'), entry[:3]) |
620 | self.assertEqual(u'./' + name0u, entry[4]) |
621 | @@ -1617,13 +1649,17 @@ |
622 | processed_links = [] |
623 | |
624 | def file_handler(from_path, to_path): |
625 | - processed_files.append(('f', from_path, to_path)) |
626 | + processed_files.append( |
627 | + ('f', osutils.normpath(from_path), osutils.normpath(to_path))) |
628 | |
629 | def dir_handler(from_path, to_path): |
630 | - processed_files.append(('d', from_path, to_path)) |
631 | + processed_files.append( |
632 | + ('d', osutils.normpath(from_path), osutils.normpath(to_path))) |
633 | |
634 | def link_handler(from_path, to_path): |
635 | - processed_links.append((from_path, to_path)) |
636 | + processed_links.append( |
637 | + (osutils.normpath(from_path), osutils.normpath(to_path))) |
638 | + |
639 | handlers = {'file': file_handler, |
640 | 'directory': dir_handler, |
641 | 'symlink': link_handler, |
642 | |
643 | === modified file 'breezy/transport/local.py' |
644 | --- breezy/transport/local.py 2022-01-12 23:43:40 +0000 |
645 | +++ breezy/transport/local.py 2022-01-18 07:08:36 +0000 |
646 | @@ -30,13 +30,12 @@ |
647 | |
648 | from breezy import ( |
649 | atomicfile, |
650 | - osutils, |
651 | urlutils, |
652 | ) |
653 | from breezy.transport import LateReadError |
654 | """) |
655 | |
656 | -from .. import transport |
657 | +from .. import osutils, transport |
658 | |
659 | |
660 | _append_flags = os.O_CREAT | os.O_APPEND | os.O_WRONLY | osutils.O_BINARY | osutils.O_NOINHERIT |
661 | @@ -154,7 +153,10 @@ |
662 | path = self._abspath(relpath) |
663 | return open(path, 'rb') |
664 | except (IOError, OSError) as e: |
665 | - if e.errno == errno.EISDIR: |
666 | + if e.errno == errno.EISDIR or ( |
667 | + (sys.platform == "win32") and (e.errno == errno.EACCES) |
668 | + and os.path.isdir(path) |
669 | + ): |
670 | return LateReadError(relpath) |
671 | self._translate_error(e, path) |
672 |
This includes changes from the merge proposal https:/ /code.launchpad .net/~a1s/ brz/3.2- windows- test_osutils/ +merge/ 412025
The other thing hat has been fixed after that is passing the regression tests run by brz selftes test_config.