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 TestSafeUtf8(TestCase):
251
def test_from_ascii_string(self):
253
self.assertEqual('foobar', osutils.safe_utf8(f))
255
def test_from_unicode_string_ascii_contents(self):
256
self.assertEqual('bargam', osutils.safe_utf8(u'bargam'))
258
def test_from_unicode_string_unicode_contents(self):
259
self.assertEqual('bargam\xc2\xae', osutils.safe_utf8(u'bargam\xae'))
261
def test_from_utf8_string(self):
262
self.assertEqual('foo\xc2\xae', osutils.safe_utf8('foo\xc2\xae'))
264
def test_bad_utf8_string(self):
265
self.assertRaises(BzrBadParameterNotUnicode,
266
osutils.safe_utf8, '\xbb\xbb')
269
class TestSafeRevisionId(TestCase):
271
def test_from_ascii_string(self):
273
self.assertEqual('foobar', osutils.safe_revision_id(f))
274
self.assertIs(osutils.safe_utf8(f), f)
276
def test_from_unicode_string_ascii_contents(self):
277
self.assertEqual('bargam', osutils.safe_revision_id(u'bargam'))
279
def test_from_unicode_string_unicode_contents(self):
280
self.assertEqual('bargam\xc2\xae',
281
osutils.safe_revision_id(u'bargam\xae'))
283
def test_from_utf8_string(self):
284
self.assertEqual('foo\xc2\xae',
285
osutils.safe_revision_id('foo\xc2\xae'))
287
def test_bad_utf8_string(self):
288
# This check may eventually go away
289
self.assertRaises(BzrBadParameterNotUnicode,
290
osutils.safe_utf8, '\xbb\xbb')
293
"""Currently, None is a valid revision_id"""
294
self.assertEqual(None, osutils.safe_revision_id(None))
297
class TestWin32Funcs(TestCase):
298
"""Test that the _win32 versions of os utilities return appropriate paths."""
300
def test_abspath(self):
301
self.assertEqual('C:/foo', osutils._win32_abspath('C:\\foo'))
302
self.assertEqual('C:/foo', osutils._win32_abspath('C:/foo'))
304
def test_realpath(self):
305
self.assertEqual('C:/foo', osutils._win32_realpath('C:\\foo'))
306
self.assertEqual('C:/foo', osutils._win32_realpath('C:/foo'))
308
def test_pathjoin(self):
309
self.assertEqual('path/to/foo', osutils._win32_pathjoin('path', 'to', 'foo'))
310
self.assertEqual('C:/foo', osutils._win32_pathjoin('path\\to', 'C:\\foo'))
311
self.assertEqual('C:/foo', osutils._win32_pathjoin('path/to', 'C:/foo'))
312
self.assertEqual('path/to/foo', osutils._win32_pathjoin('path/to/', 'foo'))
313
self.assertEqual('/foo', osutils._win32_pathjoin('C:/path/to/', '/foo'))
314
self.assertEqual('/foo', osutils._win32_pathjoin('C:\\path\\to\\', '\\foo'))
316
def test_normpath(self):
317
self.assertEqual('path/to/foo', osutils._win32_normpath(r'path\\from\..\to\.\foo'))
318
self.assertEqual('path/to/foo', osutils._win32_normpath('path//from/../to/./foo'))
320
def test_getcwd(self):
321
cwd = osutils._win32_getcwd()
322
os_cwd = os.getcwdu()
323
self.assertEqual(os_cwd[1:].replace('\\', '/'), cwd[1:])
324
# win32 is inconsistent whether it returns lower or upper case
325
# and even if it was consistent the user might type the other
326
# so we force it to uppercase
327
# running python.exe under cmd.exe return capital C:\\
328
# running win32 python inside a cygwin shell returns lowercase
329
self.assertEqual(os_cwd[0].upper(), cwd[0])
331
def test_fixdrive(self):
332
self.assertEqual('H:/foo', osutils._win32_fixdrive('h:/foo'))
333
self.assertEqual('H:/foo', osutils._win32_fixdrive('H:/foo'))
334
self.assertEqual('C:\\foo', osutils._win32_fixdrive('c:\\foo'))
337
class TestWin32FuncsDirs(TestCaseInTempDir):
338
"""Test win32 functions that create files."""
340
def test_getcwd(self):
341
# Make sure getcwd can handle unicode filenames
345
raise TestSkipped("Unable to create Unicode filename")
348
# TODO: jam 20060427 This will probably fail on Mac OSX because
349
# it will change the normalization of B\xe5gfors
350
# Consider using a different unicode character, or make
351
# osutils.getcwd() renormalize the path.
352
self.assertEndsWith(osutils._win32_getcwd(), u'mu-\xb5')
354
def test_mkdtemp(self):
355
tmpdir = osutils._win32_mkdtemp(dir='.')
356
self.assertFalse('\\' in tmpdir)
358
def test_rename(self):
366
osutils._win32_rename('b', 'a')
367
self.failUnlessExists('a')
368
self.failIfExists('b')
369
self.assertFileEqual('baz\n', 'a')
371
def test_rename_missing_file(self):
377
osutils._win32_rename('b', 'a')
378
except (IOError, OSError), e:
379
self.assertEqual(errno.ENOENT, e.errno)
380
self.assertFileEqual('foo\n', 'a')
382
def test_rename_missing_dir(self):
385
osutils._win32_rename('b', 'a')
386
except (IOError, OSError), e:
387
self.assertEqual(errno.ENOENT, e.errno)
389
def test_rename_current_dir(self):
392
# You can't rename the working directory
393
# doing rename non-existant . usually
394
# just raises ENOENT, since non-existant
397
osutils._win32_rename('b', '.')
398
except (IOError, OSError), e:
399
self.assertEqual(errno.ENOENT, e.errno)
401
def test_splitpath(self):
402
def check(expected, path):
403
self.assertEqual(expected, osutils.splitpath(path))
406
check(['a', 'b'], 'a/b')
407
check(['a', 'b'], 'a/./b')
408
check(['a', '.b'], 'a/.b')
409
check(['a', '.b'], 'a\\.b')
411
self.assertRaises(errors.BzrError, osutils.splitpath, 'a/../b')
414
class TestMacFuncsDirs(TestCaseInTempDir):
415
"""Test mac special functions that require directories."""
417
def test_getcwd(self):
418
# On Mac, this will actually create Ba\u030agfors
419
# but chdir will still work, because it accepts both paths
421
os.mkdir(u'B\xe5gfors')
423
raise TestSkipped("Unable to create Unicode filename")
425
os.chdir(u'B\xe5gfors')
426
self.assertEndsWith(osutils._mac_getcwd(), u'B\xe5gfors')
428
def test_getcwd_nonnorm(self):
429
# Test that _mac_getcwd() will normalize this path
431
os.mkdir(u'Ba\u030agfors')
433
raise TestSkipped("Unable to create Unicode filename")
435
os.chdir(u'Ba\u030agfors')
436
self.assertEndsWith(osutils._mac_getcwd(), u'B\xe5gfors')
439
class TestSplitLines(TestCase):
441
def test_split_unicode(self):
442
self.assertEqual([u'foo\n', u'bar\xae'],
443
osutils.split_lines(u'foo\nbar\xae'))
444
self.assertEqual([u'foo\n', u'bar\xae\n'],
445
osutils.split_lines(u'foo\nbar\xae\n'))
447
def test_split_with_carriage_returns(self):
448
self.assertEqual(['foo\rbar\n'],
449
osutils.split_lines('foo\rbar\n'))
452
class TestWalkDirs(TestCaseInTempDir):
454
def test_walkdirs(self):
463
self.build_tree(tree)
464
expected_dirblocks = [
466
[('0file', '0file', 'file'),
467
('1dir', '1dir', 'directory'),
468
('2file', '2file', 'file'),
472
[('1dir/0file', '0file', 'file'),
473
('1dir/1dir', '1dir', 'directory'),
476
(('1dir/1dir', './1dir/1dir'),
483
for dirdetail, dirblock in osutils.walkdirs('.'):
484
if len(dirblock) and dirblock[0][1] == '.bzr':
485
# this tests the filtering of selected paths
488
result.append((dirdetail, dirblock))
490
self.assertTrue(found_bzrdir)
491
self.assertEqual(expected_dirblocks,
492
[(dirinfo, [line[0:3] for line in block]) for dirinfo, block in result])
493
# you can search a subdir only, with a supplied prefix.
495
for dirblock in osutils.walkdirs('./1dir', '1dir'):
496
result.append(dirblock)
497
self.assertEqual(expected_dirblocks[1:],
498
[(dirinfo, [line[0:3] for line in block]) for dirinfo, block in result])
500
def assertPathCompare(self, path_less, path_greater):
501
"""check that path_less and path_greater compare correctly."""
502
self.assertEqual(0, osutils.compare_paths_prefix_order(
503
path_less, path_less))
504
self.assertEqual(0, osutils.compare_paths_prefix_order(
505
path_greater, path_greater))
506
self.assertEqual(-1, osutils.compare_paths_prefix_order(
507
path_less, path_greater))
508
self.assertEqual(1, osutils.compare_paths_prefix_order(
509
path_greater, path_less))
511
def test_compare_paths_prefix_order(self):
512
# root before all else
513
self.assertPathCompare("/", "/a")
515
self.assertPathCompare("/a", "/b")
516
self.assertPathCompare("/b", "/z")
517
# high dirs before lower.
518
self.assertPathCompare("/z", "/a/a")
519
# except if the deeper dir should be output first
520
self.assertPathCompare("/a/b/c", "/d/g")
521
# lexical betwen dirs of the same height
522
self.assertPathCompare("/a/z", "/z/z")
523
self.assertPathCompare("/a/c/z", "/a/d/e")
525
# this should also be consistent for no leading / paths
526
# root before all else
527
self.assertPathCompare("", "a")
529
self.assertPathCompare("a", "b")
530
self.assertPathCompare("b", "z")
531
# high dirs before lower.
532
self.assertPathCompare("z", "a/a")
533
# except if the deeper dir should be output first
534
self.assertPathCompare("a/b/c", "d/g")
535
# lexical betwen dirs of the same height
536
self.assertPathCompare("a/z", "z/z")
537
self.assertPathCompare("a/c/z", "a/d/e")
539
def test_path_prefix_sorting(self):
540
"""Doing a sort on path prefix should match our sample data."""
571
sorted(original_paths, key=osutils.path_prefix_key))
572
# using the comparison routine shoudl work too:
575
sorted(original_paths, cmp=osutils.compare_paths_prefix_order))
578
class TestCopyTree(TestCaseInTempDir):
580
def test_copy_basic_tree(self):
581
self.build_tree(['source/', 'source/a', 'source/b/', 'source/b/c'])
582
osutils.copy_tree('source', 'target')
583
self.assertEqual(['a', 'b'], sorted(os.listdir('target')))
584
self.assertEqual(['c'], os.listdir('target/b'))
586
def test_copy_tree_target_exists(self):
587
self.build_tree(['source/', 'source/a', 'source/b/', 'source/b/c',
589
osutils.copy_tree('source', 'target')
590
self.assertEqual(['a', 'b'], sorted(os.listdir('target')))
591
self.assertEqual(['c'], os.listdir('target/b'))
593
def test_copy_tree_symlinks(self):
594
if not osutils.has_symlinks():
596
self.build_tree(['source/'])
597
os.symlink('a/generic/path', 'source/lnk')
598
osutils.copy_tree('source', 'target')
599
self.assertEqual(['lnk'], os.listdir('target'))
600
self.assertEqual('a/generic/path', os.readlink('target/lnk'))
602
def test_copy_tree_handlers(self):
605
def file_handler(from_path, to_path):
606
processed_files.append(('f', from_path, to_path))
607
def dir_handler(from_path, to_path):
608
processed_files.append(('d', from_path, to_path))
609
def link_handler(from_path, to_path):
610
processed_links.append((from_path, to_path))
611
handlers = {'file':file_handler,
612
'directory':dir_handler,
613
'symlink':link_handler,
616
self.build_tree(['source/', 'source/a', 'source/b/', 'source/b/c'])
617
if osutils.has_symlinks():
618
os.symlink('a/generic/path', 'source/lnk')
619
osutils.copy_tree('source', 'target', handlers=handlers)
621
self.assertEqual([('d', 'source', 'target'),
622
('f', 'source/a', 'target/a'),
623
('d', 'source/b', 'target/b'),
624
('f', 'source/b/c', 'target/b/c'),
626
self.failIfExists('target')
627
if osutils.has_symlinks():
628
self.assertEqual([('source/lnk', 'target/lnk')], processed_links)
631
#class TestTerminalEncoding has been moved to test_osutils_encodings.py
632
# [bialix] 2006/12/26
635
class TestSetUnsetEnv(TestCase):
636
"""Test updating the environment"""
639
super(TestSetUnsetEnv, self).setUp()
641
self.assertEqual(None, os.environ.get('BZR_TEST_ENV_VAR'),
642
'Environment was not cleaned up properly.'
643
' Variable BZR_TEST_ENV_VAR should not exist.')
645
if 'BZR_TEST_ENV_VAR' in os.environ:
646
del os.environ['BZR_TEST_ENV_VAR']
648
self.addCleanup(cleanup)
651
"""Test that we can set an env variable"""
652
old = osutils.set_or_unset_env('BZR_TEST_ENV_VAR', 'foo')
653
self.assertEqual(None, old)
654
self.assertEqual('foo', os.environ.get('BZR_TEST_ENV_VAR'))
656
def test_double_set(self):
657
"""Test that we get the old value out"""
658
osutils.set_or_unset_env('BZR_TEST_ENV_VAR', 'foo')
659
old = osutils.set_or_unset_env('BZR_TEST_ENV_VAR', 'bar')
660
self.assertEqual('foo', old)
661
self.assertEqual('bar', os.environ.get('BZR_TEST_ENV_VAR'))
663
def test_unicode(self):
664
"""Environment can only contain plain strings
666
So Unicode strings must be encoded.
668
# Try a few different characters, to see if we can get
669
# one that will be valid in the user_encoding
670
possible_vals = [u'm\xb5', u'\xe1', u'\u0410']
671
for uni_val in possible_vals:
673
env_val = uni_val.encode(bzrlib.user_encoding)
674
except UnicodeEncodeError:
675
# Try a different character
680
raise TestSkipped('Cannot find a unicode character that works in'
681
' encoding %s' % (bzrlib.user_encoding,))
683
old = osutils.set_or_unset_env('BZR_TEST_ENV_VAR', uni_val)
684
self.assertEqual(env_val, os.environ.get('BZR_TEST_ENV_VAR'))
686
def test_unset(self):
687
"""Test that passing None will remove the env var"""
688
osutils.set_or_unset_env('BZR_TEST_ENV_VAR', 'foo')
689
old = osutils.set_or_unset_env('BZR_TEST_ENV_VAR', None)
690
self.assertEqual('foo', old)
691
self.assertEqual(None, os.environ.get('BZR_TEST_ENV_VAR'))
692
self.failIf('BZR_TEST_ENV_VAR' in os.environ)
695
class TestLocalTimeOffset(TestCase):
697
def test_local_time_offset(self):
698
"""Test that local_time_offset() returns a sane value."""
699
offset = osutils.local_time_offset()
700
self.assertTrue(isinstance(offset, int))
701
# Test that the offset is no more than a eighteen hours in
703
# Time zone handling is system specific, so it is difficult to
704
# do more specific tests, but a value outside of this range is
706
eighteen_hours = 18 * 3600
707
self.assertTrue(-eighteen_hours < offset < eighteen_hours)
709
def test_local_time_offset_with_timestamp(self):
710
"""Test that local_time_offset() works with a timestamp."""
711
offset = osutils.local_time_offset(1000000000.1234567)
712
self.assertTrue(isinstance(offset, int))
713
eighteen_hours = 18 * 3600
714
self.assertTrue(-eighteen_hours < offset < eighteen_hours)