1
# Copyright (C) 2005, 2006 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Tests for the osutils wrapper."""
30
from bzrlib.errors import BzrBadParameterNotUnicode, InvalidURL
31
from bzrlib.tests import (
39
class TestOSUtils(TestCaseInTempDir):
41
def test_fancy_rename(self):
42
# This should work everywhere
44
osutils.fancy_rename(a, b,
45
rename_func=os.rename,
46
unlink_func=os.unlink)
48
open('a', 'wb').write('something in a\n')
50
self.failIfExists('a')
51
self.failUnlessExists('b')
52
self.check_file_contents('b', 'something in a\n')
54
open('a', 'wb').write('new something in a\n')
57
self.check_file_contents('a', 'something in a\n')
59
def test_rename(self):
60
# Rename should be semi-atomic on all platforms
61
open('a', 'wb').write('something in a\n')
62
osutils.rename('a', 'b')
63
self.failIfExists('a')
64
self.failUnlessExists('b')
65
self.check_file_contents('b', 'something in a\n')
67
open('a', 'wb').write('new something in a\n')
68
osutils.rename('b', 'a')
70
self.check_file_contents('a', 'something in a\n')
72
# TODO: test fancy_rename using a MemoryTransport
74
def test_01_rand_chars_empty(self):
75
result = osutils.rand_chars(0)
76
self.assertEqual(result, '')
78
def test_02_rand_chars_100(self):
79
result = osutils.rand_chars(100)
80
self.assertEqual(len(result), 100)
81
self.assertEqual(type(result), str)
82
self.assertContainsRe(result, r'^[a-z0-9]{100}$')
84
def test_is_inside(self):
85
is_inside = osutils.is_inside
86
self.assertTrue(is_inside('src', 'src/foo.c'))
87
self.assertFalse(is_inside('src', 'srccontrol'))
88
self.assertTrue(is_inside('src', 'src/a/a/a/foo.c'))
89
self.assertTrue(is_inside('foo.c', 'foo.c'))
90
self.assertFalse(is_inside('foo.c', ''))
91
self.assertTrue(is_inside('', 'foo.c'))
93
def test_rmtree(self):
94
# Check to remove tree with read-only files/dirs
96
f = file('dir/file', 'w')
99
# would like to also try making the directory readonly, but at the
100
# moment python shutil.rmtree doesn't handle that properly - it would
101
# need to chmod the directory before removing things inside it - deferred
102
# for now -- mbp 20060505
103
# osutils.make_readonly('dir')
104
osutils.make_readonly('dir/file')
106
osutils.rmtree('dir')
108
self.failIfExists('dir/file')
109
self.failIfExists('dir')
111
def test_file_kind(self):
112
self.build_tree(['file', 'dir/'])
113
self.assertEquals('file', osutils.file_kind('file'))
114
self.assertEquals('directory', osutils.file_kind('dir/'))
115
if osutils.has_symlinks():
116
os.symlink('symlink', 'symlink')
117
self.assertEquals('symlink', osutils.file_kind('symlink'))
119
# TODO: jam 20060529 Test a block device
121
os.lstat('/dev/null')
123
if e.errno not in (errno.ENOENT,):
126
self.assertEquals('chardev', osutils.file_kind('/dev/null'))
128
mkfifo = getattr(os, 'mkfifo', None)
132
self.assertEquals('fifo', osutils.file_kind('fifo'))
136
AF_UNIX = getattr(socket, 'AF_UNIX', None)
138
s = socket.socket(AF_UNIX)
141
self.assertEquals('socket', osutils.file_kind('socket'))
145
def test_get_umask(self):
146
if sys.platform == 'win32':
147
# umask always returns '0', no way to set it
148
self.assertEqual(0, osutils.get_umask())
151
orig_umask = osutils.get_umask()
154
self.assertEqual(0222, osutils.get_umask())
156
self.assertEqual(0022, osutils.get_umask())
158
self.assertEqual(0002, osutils.get_umask())
160
self.assertEqual(0027, osutils.get_umask())
164
def assertFormatedDelta(self, expected, seconds):
165
"""Assert osutils.format_delta formats as expected"""
166
actual = osutils.format_delta(seconds)
167
self.assertEqual(expected, actual)
169
def test_format_delta(self):
170
self.assertFormatedDelta('0 seconds ago', 0)
171
self.assertFormatedDelta('1 second ago', 1)
172
self.assertFormatedDelta('10 seconds ago', 10)
173
self.assertFormatedDelta('59 seconds ago', 59)
174
self.assertFormatedDelta('89 seconds ago', 89)
175
self.assertFormatedDelta('1 minute, 30 seconds ago', 90)
176
self.assertFormatedDelta('3 minutes, 0 seconds ago', 180)
177
self.assertFormatedDelta('3 minutes, 1 second ago', 181)
178
self.assertFormatedDelta('10 minutes, 15 seconds ago', 615)
179
self.assertFormatedDelta('30 minutes, 59 seconds ago', 1859)
180
self.assertFormatedDelta('31 minutes, 0 seconds ago', 1860)
181
self.assertFormatedDelta('60 minutes, 0 seconds ago', 3600)
182
self.assertFormatedDelta('89 minutes, 59 seconds ago', 5399)
183
self.assertFormatedDelta('1 hour, 30 minutes ago', 5400)
184
self.assertFormatedDelta('2 hours, 30 minutes ago', 9017)
185
self.assertFormatedDelta('10 hours, 0 minutes ago', 36000)
186
self.assertFormatedDelta('24 hours, 0 minutes ago', 86400)
187
self.assertFormatedDelta('35 hours, 59 minutes ago', 129599)
188
self.assertFormatedDelta('36 hours, 0 minutes ago', 129600)
189
self.assertFormatedDelta('36 hours, 0 minutes ago', 129601)
190
self.assertFormatedDelta('36 hours, 1 minute ago', 129660)
191
self.assertFormatedDelta('36 hours, 1 minute ago', 129661)
192
self.assertFormatedDelta('84 hours, 10 minutes ago', 303002)
194
# We handle when time steps the wrong direction because computers
195
# don't have synchronized clocks.
196
self.assertFormatedDelta('84 hours, 10 minutes in the future', -303002)
197
self.assertFormatedDelta('1 second in the future', -1)
198
self.assertFormatedDelta('2 seconds in the future', -2)
200
def test_dereference_path(self):
201
if not osutils.has_symlinks():
202
raise TestSkipped('Symlinks are not supported on this platform')
203
cwd = osutils.realpath('.')
205
bar_path = osutils.pathjoin(cwd, 'bar')
206
# Using './' to avoid bug #1213894 (first path component not
207
# dereferenced) in Python 2.4.1 and earlier
208
self.assertEqual(bar_path, osutils.realpath('./bar'))
209
os.symlink('bar', 'foo')
210
self.assertEqual(bar_path, osutils.realpath('./foo'))
212
# Does not dereference terminal symlinks
213
foo_path = osutils.pathjoin(cwd, 'foo')
214
self.assertEqual(foo_path, osutils.dereference_path('./foo'))
216
# Dereferences parent symlinks
218
baz_path = osutils.pathjoin(bar_path, 'baz')
219
self.assertEqual(baz_path, osutils.dereference_path('./foo/baz'))
221
# Dereferences parent symlinks that are the first path element
222
self.assertEqual(baz_path, osutils.dereference_path('foo/baz'))
224
# Dereferences parent symlinks in absolute paths
225
foo_baz_path = osutils.pathjoin(foo_path, 'baz')
226
self.assertEqual(baz_path, osutils.dereference_path(foo_baz_path))
229
class TestSafeUnicode(TestCase):
231
def test_from_ascii_string(self):
232
self.assertEqual(u'foobar', osutils.safe_unicode('foobar'))
234
def test_from_unicode_string_ascii_contents(self):
235
self.assertEqual(u'bargam', osutils.safe_unicode(u'bargam'))
237
def test_from_unicode_string_unicode_contents(self):
238
self.assertEqual(u'bargam\xae', osutils.safe_unicode(u'bargam\xae'))
240
def test_from_utf8_string(self):
241
self.assertEqual(u'foo\xae', osutils.safe_unicode('foo\xc2\xae'))
243
def test_bad_utf8_string(self):
244
self.assertRaises(BzrBadParameterNotUnicode,
245
osutils.safe_unicode,
249
class TestWin32Funcs(TestCase):
250
"""Test that the _win32 versions of os utilities return appropriate paths."""
252
def test_abspath(self):
253
self.assertEqual('C:/foo', osutils._win32_abspath('C:\\foo'))
254
self.assertEqual('C:/foo', osutils._win32_abspath('C:/foo'))
256
def test_realpath(self):
257
self.assertEqual('C:/foo', osutils._win32_realpath('C:\\foo'))
258
self.assertEqual('C:/foo', osutils._win32_realpath('C:/foo'))
260
def test_pathjoin(self):
261
self.assertEqual('path/to/foo', osutils._win32_pathjoin('path', 'to', 'foo'))
262
self.assertEqual('C:/foo', osutils._win32_pathjoin('path\\to', 'C:\\foo'))
263
self.assertEqual('C:/foo', osutils._win32_pathjoin('path/to', 'C:/foo'))
264
self.assertEqual('path/to/foo', osutils._win32_pathjoin('path/to/', 'foo'))
265
self.assertEqual('/foo', osutils._win32_pathjoin('C:/path/to/', '/foo'))
266
self.assertEqual('/foo', osutils._win32_pathjoin('C:\\path\\to\\', '\\foo'))
268
def test_normpath(self):
269
self.assertEqual('path/to/foo', osutils._win32_normpath(r'path\\from\..\to\.\foo'))
270
self.assertEqual('path/to/foo', osutils._win32_normpath('path//from/../to/./foo'))
272
def test_getcwd(self):
273
cwd = osutils._win32_getcwd()
274
os_cwd = os.getcwdu()
275
self.assertEqual(os_cwd[1:].replace('\\', '/'), cwd[1:])
276
# win32 is inconsistent whether it returns lower or upper case
277
# and even if it was consistent the user might type the other
278
# so we force it to uppercase
279
# running python.exe under cmd.exe return capital C:\\
280
# running win32 python inside a cygwin shell returns lowercase
281
self.assertEqual(os_cwd[0].upper(), cwd[0])
283
def test_fixdrive(self):
284
self.assertEqual('H:/foo', osutils._win32_fixdrive('h:/foo'))
285
self.assertEqual('H:/foo', osutils._win32_fixdrive('H:/foo'))
286
self.assertEqual('C:\\foo', osutils._win32_fixdrive('c:\\foo'))
289
class TestWin32FuncsDirs(TestCaseInTempDir):
290
"""Test win32 functions that create files."""
292
def test_getcwd(self):
293
# Make sure getcwd can handle unicode filenames
297
raise TestSkipped("Unable to create Unicode filename")
300
# TODO: jam 20060427 This will probably fail on Mac OSX because
301
# it will change the normalization of B\xe5gfors
302
# Consider using a different unicode character, or make
303
# osutils.getcwd() renormalize the path.
304
self.assertEndsWith(osutils._win32_getcwd(), u'mu-\xb5')
306
def test_mkdtemp(self):
307
tmpdir = osutils._win32_mkdtemp(dir='.')
308
self.assertFalse('\\' in tmpdir)
310
def test_rename(self):
318
osutils._win32_rename('b', 'a')
319
self.failUnlessExists('a')
320
self.failIfExists('b')
321
self.assertFileEqual('baz\n', 'a')
323
def test_rename_missing_file(self):
329
osutils._win32_rename('b', 'a')
330
except (IOError, OSError), e:
331
self.assertEqual(errno.ENOENT, e.errno)
332
self.assertFileEqual('foo\n', 'a')
334
def test_rename_missing_dir(self):
337
osutils._win32_rename('b', 'a')
338
except (IOError, OSError), e:
339
self.assertEqual(errno.ENOENT, e.errno)
341
def test_rename_current_dir(self):
344
# You can't rename the working directory
345
# doing rename non-existant . usually
346
# just raises ENOENT, since non-existant
349
osutils._win32_rename('b', '.')
350
except (IOError, OSError), e:
351
self.assertEqual(errno.ENOENT, e.errno)
353
def test_splitpath(self):
354
def check(expected, path):
355
self.assertEqual(expected, osutils.splitpath(path))
358
check(['a', 'b'], 'a/b')
359
check(['a', 'b'], 'a/./b')
360
check(['a', '.b'], 'a/.b')
361
check(['a', '.b'], 'a\\.b')
363
self.assertRaises(errors.BzrError, osutils.splitpath, 'a/../b')
366
class TestMacFuncsDirs(TestCaseInTempDir):
367
"""Test mac special functions that require directories."""
369
def test_getcwd(self):
370
# On Mac, this will actually create Ba\u030agfors
371
# but chdir will still work, because it accepts both paths
373
os.mkdir(u'B\xe5gfors')
375
raise TestSkipped("Unable to create Unicode filename")
377
os.chdir(u'B\xe5gfors')
378
self.assertEndsWith(osutils._mac_getcwd(), u'B\xe5gfors')
380
def test_getcwd_nonnorm(self):
381
# Test that _mac_getcwd() will normalize this path
383
os.mkdir(u'Ba\u030agfors')
385
raise TestSkipped("Unable to create Unicode filename")
387
os.chdir(u'Ba\u030agfors')
388
self.assertEndsWith(osutils._mac_getcwd(), u'B\xe5gfors')
391
class TestSplitLines(TestCase):
393
def test_split_unicode(self):
394
self.assertEqual([u'foo\n', u'bar\xae'],
395
osutils.split_lines(u'foo\nbar\xae'))
396
self.assertEqual([u'foo\n', u'bar\xae\n'],
397
osutils.split_lines(u'foo\nbar\xae\n'))
399
def test_split_with_carriage_returns(self):
400
self.assertEqual(['foo\rbar\n'],
401
osutils.split_lines('foo\rbar\n'))
404
class TestWalkDirs(TestCaseInTempDir):
406
def test_walkdirs(self):
415
self.build_tree(tree)
416
expected_dirblocks = [
418
[('0file', '0file', 'file'),
419
('1dir', '1dir', 'directory'),
420
('2file', '2file', 'file'),
424
[('1dir/0file', '0file', 'file'),
425
('1dir/1dir', '1dir', 'directory'),
428
(('1dir/1dir', './1dir/1dir'),
435
for dirdetail, dirblock in osutils.walkdirs('.'):
436
if len(dirblock) and dirblock[0][1] == '.bzr':
437
# this tests the filtering of selected paths
440
result.append((dirdetail, dirblock))
442
self.assertTrue(found_bzrdir)
443
self.assertEqual(expected_dirblocks,
444
[(dirinfo, [line[0:3] for line in block]) for dirinfo, block in result])
445
# you can search a subdir only, with a supplied prefix.
447
for dirblock in osutils.walkdirs('./1dir', '1dir'):
448
result.append(dirblock)
449
self.assertEqual(expected_dirblocks[1:],
450
[(dirinfo, [line[0:3] for line in block]) for dirinfo, block in result])
452
def assertPathCompare(self, path_less, path_greater):
453
"""check that path_less and path_greater compare correctly."""
454
self.assertEqual(0, osutils.compare_paths_prefix_order(
455
path_less, path_less))
456
self.assertEqual(0, osutils.compare_paths_prefix_order(
457
path_greater, path_greater))
458
self.assertEqual(-1, osutils.compare_paths_prefix_order(
459
path_less, path_greater))
460
self.assertEqual(1, osutils.compare_paths_prefix_order(
461
path_greater, path_less))
463
def test_compare_paths_prefix_order(self):
464
# root before all else
465
self.assertPathCompare("/", "/a")
467
self.assertPathCompare("/a", "/b")
468
self.assertPathCompare("/b", "/z")
469
# high dirs before lower.
470
self.assertPathCompare("/z", "/a/a")
471
# except if the deeper dir should be output first
472
self.assertPathCompare("/a/b/c", "/d/g")
473
# lexical betwen dirs of the same height
474
self.assertPathCompare("/a/z", "/z/z")
475
self.assertPathCompare("/a/c/z", "/a/d/e")
477
# this should also be consistent for no leading / paths
478
# root before all else
479
self.assertPathCompare("", "a")
481
self.assertPathCompare("a", "b")
482
self.assertPathCompare("b", "z")
483
# high dirs before lower.
484
self.assertPathCompare("z", "a/a")
485
# except if the deeper dir should be output first
486
self.assertPathCompare("a/b/c", "d/g")
487
# lexical betwen dirs of the same height
488
self.assertPathCompare("a/z", "z/z")
489
self.assertPathCompare("a/c/z", "a/d/e")
491
def test_path_prefix_sorting(self):
492
"""Doing a sort on path prefix should match our sample data."""
523
sorted(original_paths, key=osutils.path_prefix_key))
524
# using the comparison routine shoudl work too:
527
sorted(original_paths, cmp=osutils.compare_paths_prefix_order))
530
class TestCopyTree(TestCaseInTempDir):
532
def test_copy_basic_tree(self):
533
self.build_tree(['source/', 'source/a', 'source/b/', 'source/b/c'])
534
osutils.copy_tree('source', 'target')
535
self.assertEqual(['a', 'b'], sorted(os.listdir('target')))
536
self.assertEqual(['c'], os.listdir('target/b'))
538
def test_copy_tree_target_exists(self):
539
self.build_tree(['source/', 'source/a', 'source/b/', 'source/b/c',
541
osutils.copy_tree('source', 'target')
542
self.assertEqual(['a', 'b'], sorted(os.listdir('target')))
543
self.assertEqual(['c'], os.listdir('target/b'))
545
def test_copy_tree_symlinks(self):
546
if not osutils.has_symlinks():
548
self.build_tree(['source/'])
549
os.symlink('a/generic/path', 'source/lnk')
550
osutils.copy_tree('source', 'target')
551
self.assertEqual(['lnk'], os.listdir('target'))
552
self.assertEqual('a/generic/path', os.readlink('target/lnk'))
554
def test_copy_tree_handlers(self):
557
def file_handler(from_path, to_path):
558
processed_files.append(('f', from_path, to_path))
559
def dir_handler(from_path, to_path):
560
processed_files.append(('d', from_path, to_path))
561
def link_handler(from_path, to_path):
562
processed_links.append((from_path, to_path))
563
handlers = {'file':file_handler,
564
'directory':dir_handler,
565
'symlink':link_handler,
568
self.build_tree(['source/', 'source/a', 'source/b/', 'source/b/c'])
569
if osutils.has_symlinks():
570
os.symlink('a/generic/path', 'source/lnk')
571
osutils.copy_tree('source', 'target', handlers=handlers)
573
self.assertEqual([('d', 'source', 'target'),
574
('f', 'source/a', 'target/a'),
575
('d', 'source/b', 'target/b'),
576
('f', 'source/b/c', 'target/b/c'),
578
self.failIfExists('target')
579
if osutils.has_symlinks():
580
self.assertEqual([('source/lnk', 'target/lnk')], processed_links)
583
class TestTerminalEncoding(TestCase):
584
"""Test the auto-detection of proper terminal encoding."""
587
self._stdout = sys.stdout
588
self._stderr = sys.stderr
589
self._stdin = sys.stdin
590
self._user_encoding = bzrlib.user_encoding
592
self.addCleanup(self._reset)
594
sys.stdout = StringIOWrapper()
595
sys.stdout.encoding = 'stdout_encoding'
596
sys.stderr = StringIOWrapper()
597
sys.stderr.encoding = 'stderr_encoding'
598
sys.stdin = StringIOWrapper()
599
sys.stdin.encoding = 'stdin_encoding'
600
bzrlib.user_encoding = 'user_encoding'
603
sys.stdout = self._stdout
604
sys.stderr = self._stderr
605
sys.stdin = self._stdin
606
bzrlib.user_encoding = self._user_encoding
608
def test_get_terminal_encoding(self):
609
# first preference is stdout encoding
610
self.assertEqual('stdout_encoding', osutils.get_terminal_encoding())
612
sys.stdout.encoding = None
613
# if sys.stdout is None, fall back to sys.stdin
614
self.assertEqual('stdin_encoding', osutils.get_terminal_encoding())
616
sys.stdin.encoding = None
617
# and in the worst case, use bzrlib.user_encoding
618
self.assertEqual('user_encoding', osutils.get_terminal_encoding())
621
class TestSetUnsetEnv(TestCase):
622
"""Test updating the environment"""
625
super(TestSetUnsetEnv, self).setUp()
627
self.assertEqual(None, os.environ.get('BZR_TEST_ENV_VAR'),
628
'Environment was not cleaned up properly.'
629
' Variable BZR_TEST_ENV_VAR should not exist.')
631
if 'BZR_TEST_ENV_VAR' in os.environ:
632
del os.environ['BZR_TEST_ENV_VAR']
634
self.addCleanup(cleanup)
637
"""Test that we can set an env variable"""
638
old = osutils.set_or_unset_env('BZR_TEST_ENV_VAR', 'foo')
639
self.assertEqual(None, old)
640
self.assertEqual('foo', os.environ.get('BZR_TEST_ENV_VAR'))
642
def test_double_set(self):
643
"""Test that we get the old value out"""
644
osutils.set_or_unset_env('BZR_TEST_ENV_VAR', 'foo')
645
old = osutils.set_or_unset_env('BZR_TEST_ENV_VAR', 'bar')
646
self.assertEqual('foo', old)
647
self.assertEqual('bar', os.environ.get('BZR_TEST_ENV_VAR'))
649
def test_unicode(self):
650
"""Environment can only contain plain strings
652
So Unicode strings must be encoded.
654
# Try a few different characters, to see if we can get
655
# one that will be valid in the user_encoding
656
possible_vals = [u'm\xb5', u'\xe1', u'\u0410']
657
for uni_val in possible_vals:
659
env_val = uni_val.encode(bzrlib.user_encoding)
660
except UnicodeEncodeError:
661
# Try a different character
666
raise TestSkipped('Cannot find a unicode character that works in'
667
' encoding %s' % (bzrlib.user_encoding,))
669
old = osutils.set_or_unset_env('BZR_TEST_ENV_VAR', uni_val)
670
self.assertEqual(env_val, os.environ.get('BZR_TEST_ENV_VAR'))
672
def test_unset(self):
673
"""Test that passing None will remove the env var"""
674
osutils.set_or_unset_env('BZR_TEST_ENV_VAR', 'foo')
675
old = osutils.set_or_unset_env('BZR_TEST_ENV_VAR', None)
676
self.assertEqual('foo', old)
677
self.assertEqual(None, os.environ.get('BZR_TEST_ENV_VAR'))
678
self.failIf('BZR_TEST_ENV_VAR' in os.environ)