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_contains_whitespace(self):
42
self.failUnless(osutils.contains_whitespace(u' '))
43
self.failUnless(osutils.contains_whitespace(u'hello there'))
44
self.failUnless(osutils.contains_whitespace(u'hellothere\n'))
45
self.failUnless(osutils.contains_whitespace(u'hello\nthere'))
46
self.failUnless(osutils.contains_whitespace(u'hello\rthere'))
47
self.failUnless(osutils.contains_whitespace(u'hello\tthere'))
49
# \xa0 is "Non-breaking-space" which on some python locales thinks it
50
# is whitespace, but we do not.
51
self.failIf(osutils.contains_whitespace(u''))
52
self.failIf(osutils.contains_whitespace(u'hellothere'))
53
self.failIf(osutils.contains_whitespace(u'hello\xa0there'))
55
def test_fancy_rename(self):
56
# This should work everywhere
58
osutils.fancy_rename(a, b,
59
rename_func=os.rename,
60
unlink_func=os.unlink)
62
open('a', 'wb').write('something in a\n')
64
self.failIfExists('a')
65
self.failUnlessExists('b')
66
self.check_file_contents('b', 'something in a\n')
68
open('a', 'wb').write('new something in a\n')
71
self.check_file_contents('a', 'something in a\n')
73
def test_rename(self):
74
# Rename should be semi-atomic on all platforms
75
open('a', 'wb').write('something in a\n')
76
osutils.rename('a', 'b')
77
self.failIfExists('a')
78
self.failUnlessExists('b')
79
self.check_file_contents('b', 'something in a\n')
81
open('a', 'wb').write('new something in a\n')
82
osutils.rename('b', 'a')
84
self.check_file_contents('a', 'something in a\n')
86
# TODO: test fancy_rename using a MemoryTransport
88
def test_01_rand_chars_empty(self):
89
result = osutils.rand_chars(0)
90
self.assertEqual(result, '')
92
def test_02_rand_chars_100(self):
93
result = osutils.rand_chars(100)
94
self.assertEqual(len(result), 100)
95
self.assertEqual(type(result), str)
96
self.assertContainsRe(result, r'^[a-z0-9]{100}$')
98
def test_is_inside(self):
99
is_inside = osutils.is_inside
100
self.assertTrue(is_inside('src', 'src/foo.c'))
101
self.assertFalse(is_inside('src', 'srccontrol'))
102
self.assertTrue(is_inside('src', 'src/a/a/a/foo.c'))
103
self.assertTrue(is_inside('foo.c', 'foo.c'))
104
self.assertFalse(is_inside('foo.c', ''))
105
self.assertTrue(is_inside('', 'foo.c'))
107
def test_rmtree(self):
108
# Check to remove tree with read-only files/dirs
110
f = file('dir/file', 'w')
113
# would like to also try making the directory readonly, but at the
114
# moment python shutil.rmtree doesn't handle that properly - it would
115
# need to chmod the directory before removing things inside it - deferred
116
# for now -- mbp 20060505
117
# osutils.make_readonly('dir')
118
osutils.make_readonly('dir/file')
120
osutils.rmtree('dir')
122
self.failIfExists('dir/file')
123
self.failIfExists('dir')
125
def test_file_kind(self):
126
self.build_tree(['file', 'dir/'])
127
self.assertEquals('file', osutils.file_kind('file'))
128
self.assertEquals('directory', osutils.file_kind('dir/'))
129
if osutils.has_symlinks():
130
os.symlink('symlink', 'symlink')
131
self.assertEquals('symlink', osutils.file_kind('symlink'))
133
# TODO: jam 20060529 Test a block device
135
os.lstat('/dev/null')
137
if e.errno not in (errno.ENOENT,):
140
self.assertEquals('chardev', osutils.file_kind('/dev/null'))
142
mkfifo = getattr(os, 'mkfifo', None)
146
self.assertEquals('fifo', osutils.file_kind('fifo'))
150
AF_UNIX = getattr(socket, 'AF_UNIX', None)
152
s = socket.socket(AF_UNIX)
155
self.assertEquals('socket', osutils.file_kind('socket'))
159
def test_get_umask(self):
160
if sys.platform == 'win32':
161
# umask always returns '0', no way to set it
162
self.assertEqual(0, osutils.get_umask())
165
orig_umask = osutils.get_umask()
168
self.assertEqual(0222, osutils.get_umask())
170
self.assertEqual(0022, osutils.get_umask())
172
self.assertEqual(0002, osutils.get_umask())
174
self.assertEqual(0027, osutils.get_umask())
178
def assertFormatedDelta(self, expected, seconds):
179
"""Assert osutils.format_delta formats as expected"""
180
actual = osutils.format_delta(seconds)
181
self.assertEqual(expected, actual)
183
def test_format_delta(self):
184
self.assertFormatedDelta('0 seconds ago', 0)
185
self.assertFormatedDelta('1 second ago', 1)
186
self.assertFormatedDelta('10 seconds ago', 10)
187
self.assertFormatedDelta('59 seconds ago', 59)
188
self.assertFormatedDelta('89 seconds ago', 89)
189
self.assertFormatedDelta('1 minute, 30 seconds ago', 90)
190
self.assertFormatedDelta('3 minutes, 0 seconds ago', 180)
191
self.assertFormatedDelta('3 minutes, 1 second ago', 181)
192
self.assertFormatedDelta('10 minutes, 15 seconds ago', 615)
193
self.assertFormatedDelta('30 minutes, 59 seconds ago', 1859)
194
self.assertFormatedDelta('31 minutes, 0 seconds ago', 1860)
195
self.assertFormatedDelta('60 minutes, 0 seconds ago', 3600)
196
self.assertFormatedDelta('89 minutes, 59 seconds ago', 5399)
197
self.assertFormatedDelta('1 hour, 30 minutes ago', 5400)
198
self.assertFormatedDelta('2 hours, 30 minutes ago', 9017)
199
self.assertFormatedDelta('10 hours, 0 minutes ago', 36000)
200
self.assertFormatedDelta('24 hours, 0 minutes ago', 86400)
201
self.assertFormatedDelta('35 hours, 59 minutes ago', 129599)
202
self.assertFormatedDelta('36 hours, 0 minutes ago', 129600)
203
self.assertFormatedDelta('36 hours, 0 minutes ago', 129601)
204
self.assertFormatedDelta('36 hours, 1 minute ago', 129660)
205
self.assertFormatedDelta('36 hours, 1 minute ago', 129661)
206
self.assertFormatedDelta('84 hours, 10 minutes ago', 303002)
208
# We handle when time steps the wrong direction because computers
209
# don't have synchronized clocks.
210
self.assertFormatedDelta('84 hours, 10 minutes in the future', -303002)
211
self.assertFormatedDelta('1 second in the future', -1)
212
self.assertFormatedDelta('2 seconds in the future', -2)
214
def test_dereference_path(self):
215
if not osutils.has_symlinks():
216
raise TestSkipped('Symlinks are not supported on this platform')
217
cwd = osutils.realpath('.')
219
bar_path = osutils.pathjoin(cwd, 'bar')
220
# Using './' to avoid bug #1213894 (first path component not
221
# dereferenced) in Python 2.4.1 and earlier
222
self.assertEqual(bar_path, osutils.realpath('./bar'))
223
os.symlink('bar', 'foo')
224
self.assertEqual(bar_path, osutils.realpath('./foo'))
226
# Does not dereference terminal symlinks
227
foo_path = osutils.pathjoin(cwd, 'foo')
228
self.assertEqual(foo_path, osutils.dereference_path('./foo'))
230
# Dereferences parent symlinks
232
baz_path = osutils.pathjoin(bar_path, 'baz')
233
self.assertEqual(baz_path, osutils.dereference_path('./foo/baz'))
235
# Dereferences parent symlinks that are the first path element
236
self.assertEqual(baz_path, osutils.dereference_path('foo/baz'))
238
# Dereferences parent symlinks in absolute paths
239
foo_baz_path = osutils.pathjoin(foo_path, 'baz')
240
self.assertEqual(baz_path, osutils.dereference_path(foo_baz_path))
243
class TestSafeUnicode(TestCase):
245
def test_from_ascii_string(self):
246
self.assertEqual(u'foobar', osutils.safe_unicode('foobar'))
248
def test_from_unicode_string_ascii_contents(self):
249
self.assertEqual(u'bargam', osutils.safe_unicode(u'bargam'))
251
def test_from_unicode_string_unicode_contents(self):
252
self.assertEqual(u'bargam\xae', osutils.safe_unicode(u'bargam\xae'))
254
def test_from_utf8_string(self):
255
self.assertEqual(u'foo\xae', osutils.safe_unicode('foo\xc2\xae'))
257
def test_bad_utf8_string(self):
258
self.assertRaises(BzrBadParameterNotUnicode,
259
osutils.safe_unicode,
263
class TestWin32Funcs(TestCase):
264
"""Test that the _win32 versions of os utilities return appropriate paths."""
266
def test_abspath(self):
267
self.assertEqual('C:/foo', osutils._win32_abspath('C:\\foo'))
268
self.assertEqual('C:/foo', osutils._win32_abspath('C:/foo'))
270
def test_realpath(self):
271
self.assertEqual('C:/foo', osutils._win32_realpath('C:\\foo'))
272
self.assertEqual('C:/foo', osutils._win32_realpath('C:/foo'))
274
def test_pathjoin(self):
275
self.assertEqual('path/to/foo', osutils._win32_pathjoin('path', 'to', 'foo'))
276
self.assertEqual('C:/foo', osutils._win32_pathjoin('path\\to', 'C:\\foo'))
277
self.assertEqual('C:/foo', osutils._win32_pathjoin('path/to', 'C:/foo'))
278
self.assertEqual('path/to/foo', osutils._win32_pathjoin('path/to/', 'foo'))
279
self.assertEqual('/foo', osutils._win32_pathjoin('C:/path/to/', '/foo'))
280
self.assertEqual('/foo', osutils._win32_pathjoin('C:\\path\\to\\', '\\foo'))
282
def test_normpath(self):
283
self.assertEqual('path/to/foo', osutils._win32_normpath(r'path\\from\..\to\.\foo'))
284
self.assertEqual('path/to/foo', osutils._win32_normpath('path//from/../to/./foo'))
286
def test_getcwd(self):
287
cwd = osutils._win32_getcwd()
288
os_cwd = os.getcwdu()
289
self.assertEqual(os_cwd[1:].replace('\\', '/'), cwd[1:])
290
# win32 is inconsistent whether it returns lower or upper case
291
# and even if it was consistent the user might type the other
292
# so we force it to uppercase
293
# running python.exe under cmd.exe return capital C:\\
294
# running win32 python inside a cygwin shell returns lowercase
295
self.assertEqual(os_cwd[0].upper(), cwd[0])
297
def test_fixdrive(self):
298
self.assertEqual('H:/foo', osutils._win32_fixdrive('h:/foo'))
299
self.assertEqual('H:/foo', osutils._win32_fixdrive('H:/foo'))
300
self.assertEqual('C:\\foo', osutils._win32_fixdrive('c:\\foo'))
303
class TestWin32FuncsDirs(TestCaseInTempDir):
304
"""Test win32 functions that create files."""
306
def test_getcwd(self):
307
# Make sure getcwd can handle unicode filenames
311
raise TestSkipped("Unable to create Unicode filename")
314
# TODO: jam 20060427 This will probably fail on Mac OSX because
315
# it will change the normalization of B\xe5gfors
316
# Consider using a different unicode character, or make
317
# osutils.getcwd() renormalize the path.
318
self.assertEndsWith(osutils._win32_getcwd(), u'mu-\xb5')
320
def test_mkdtemp(self):
321
tmpdir = osutils._win32_mkdtemp(dir='.')
322
self.assertFalse('\\' in tmpdir)
324
def test_rename(self):
332
osutils._win32_rename('b', 'a')
333
self.failUnlessExists('a')
334
self.failIfExists('b')
335
self.assertFileEqual('baz\n', 'a')
337
def test_rename_missing_file(self):
343
osutils._win32_rename('b', 'a')
344
except (IOError, OSError), e:
345
self.assertEqual(errno.ENOENT, e.errno)
346
self.assertFileEqual('foo\n', 'a')
348
def test_rename_missing_dir(self):
351
osutils._win32_rename('b', 'a')
352
except (IOError, OSError), e:
353
self.assertEqual(errno.ENOENT, e.errno)
355
def test_rename_current_dir(self):
358
# You can't rename the working directory
359
# doing rename non-existant . usually
360
# just raises ENOENT, since non-existant
363
osutils._win32_rename('b', '.')
364
except (IOError, OSError), e:
365
self.assertEqual(errno.ENOENT, e.errno)
367
def test_splitpath(self):
368
def check(expected, path):
369
self.assertEqual(expected, osutils.splitpath(path))
372
check(['a', 'b'], 'a/b')
373
check(['a', 'b'], 'a/./b')
374
check(['a', '.b'], 'a/.b')
375
check(['a', '.b'], 'a\\.b')
377
self.assertRaises(errors.BzrError, osutils.splitpath, 'a/../b')
380
class TestMacFuncsDirs(TestCaseInTempDir):
381
"""Test mac special functions that require directories."""
383
def test_getcwd(self):
384
# On Mac, this will actually create Ba\u030agfors
385
# but chdir will still work, because it accepts both paths
387
os.mkdir(u'B\xe5gfors')
389
raise TestSkipped("Unable to create Unicode filename")
391
os.chdir(u'B\xe5gfors')
392
self.assertEndsWith(osutils._mac_getcwd(), u'B\xe5gfors')
394
def test_getcwd_nonnorm(self):
395
# Test that _mac_getcwd() will normalize this path
397
os.mkdir(u'Ba\u030agfors')
399
raise TestSkipped("Unable to create Unicode filename")
401
os.chdir(u'Ba\u030agfors')
402
self.assertEndsWith(osutils._mac_getcwd(), u'B\xe5gfors')
405
class TestSplitLines(TestCase):
407
def test_split_unicode(self):
408
self.assertEqual([u'foo\n', u'bar\xae'],
409
osutils.split_lines(u'foo\nbar\xae'))
410
self.assertEqual([u'foo\n', u'bar\xae\n'],
411
osutils.split_lines(u'foo\nbar\xae\n'))
413
def test_split_with_carriage_returns(self):
414
self.assertEqual(['foo\rbar\n'],
415
osutils.split_lines('foo\rbar\n'))
418
class TestWalkDirs(TestCaseInTempDir):
420
def test_walkdirs(self):
429
self.build_tree(tree)
430
expected_dirblocks = [
432
[('0file', '0file', 'file'),
433
('1dir', '1dir', 'directory'),
434
('2file', '2file', 'file'),
438
[('1dir/0file', '0file', 'file'),
439
('1dir/1dir', '1dir', 'directory'),
442
(('1dir/1dir', './1dir/1dir'),
449
for dirdetail, dirblock in osutils.walkdirs('.'):
450
if len(dirblock) and dirblock[0][1] == '.bzr':
451
# this tests the filtering of selected paths
454
result.append((dirdetail, dirblock))
456
self.assertTrue(found_bzrdir)
457
self.assertEqual(expected_dirblocks,
458
[(dirinfo, [line[0:3] for line in block]) for dirinfo, block in result])
459
# you can search a subdir only, with a supplied prefix.
461
for dirblock in osutils.walkdirs('./1dir', '1dir'):
462
result.append(dirblock)
463
self.assertEqual(expected_dirblocks[1:],
464
[(dirinfo, [line[0:3] for line in block]) for dirinfo, block in result])
466
def assertPathCompare(self, path_less, path_greater):
467
"""check that path_less and path_greater compare correctly."""
468
self.assertEqual(0, osutils.compare_paths_prefix_order(
469
path_less, path_less))
470
self.assertEqual(0, osutils.compare_paths_prefix_order(
471
path_greater, path_greater))
472
self.assertEqual(-1, osutils.compare_paths_prefix_order(
473
path_less, path_greater))
474
self.assertEqual(1, osutils.compare_paths_prefix_order(
475
path_greater, path_less))
477
def test_compare_paths_prefix_order(self):
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
# this should also be consistent for no leading / paths
492
# root before all else
493
self.assertPathCompare("", "a")
495
self.assertPathCompare("a", "b")
496
self.assertPathCompare("b", "z")
497
# high dirs before lower.
498
self.assertPathCompare("z", "a/a")
499
# except if the deeper dir should be output first
500
self.assertPathCompare("a/b/c", "d/g")
501
# lexical betwen dirs of the same height
502
self.assertPathCompare("a/z", "z/z")
503
self.assertPathCompare("a/c/z", "a/d/e")
505
def test_path_prefix_sorting(self):
506
"""Doing a sort on path prefix should match our sample data."""
537
sorted(original_paths, key=osutils.path_prefix_key))
538
# using the comparison routine shoudl work too:
541
sorted(original_paths, cmp=osutils.compare_paths_prefix_order))
544
class TestCopyTree(TestCaseInTempDir):
546
def test_copy_basic_tree(self):
547
self.build_tree(['source/', 'source/a', 'source/b/', 'source/b/c'])
548
osutils.copy_tree('source', 'target')
549
self.assertEqual(['a', 'b'], sorted(os.listdir('target')))
550
self.assertEqual(['c'], os.listdir('target/b'))
552
def test_copy_tree_target_exists(self):
553
self.build_tree(['source/', 'source/a', 'source/b/', 'source/b/c',
555
osutils.copy_tree('source', 'target')
556
self.assertEqual(['a', 'b'], sorted(os.listdir('target')))
557
self.assertEqual(['c'], os.listdir('target/b'))
559
def test_copy_tree_symlinks(self):
560
if not osutils.has_symlinks():
562
self.build_tree(['source/'])
563
os.symlink('a/generic/path', 'source/lnk')
564
osutils.copy_tree('source', 'target')
565
self.assertEqual(['lnk'], os.listdir('target'))
566
self.assertEqual('a/generic/path', os.readlink('target/lnk'))
568
def test_copy_tree_handlers(self):
571
def file_handler(from_path, to_path):
572
processed_files.append(('f', from_path, to_path))
573
def dir_handler(from_path, to_path):
574
processed_files.append(('d', from_path, to_path))
575
def link_handler(from_path, to_path):
576
processed_links.append((from_path, to_path))
577
handlers = {'file':file_handler,
578
'directory':dir_handler,
579
'symlink':link_handler,
582
self.build_tree(['source/', 'source/a', 'source/b/', 'source/b/c'])
583
if osutils.has_symlinks():
584
os.symlink('a/generic/path', 'source/lnk')
585
osutils.copy_tree('source', 'target', handlers=handlers)
587
self.assertEqual([('d', 'source', 'target'),
588
('f', 'source/a', 'target/a'),
589
('d', 'source/b', 'target/b'),
590
('f', 'source/b/c', 'target/b/c'),
592
self.failIfExists('target')
593
if osutils.has_symlinks():
594
self.assertEqual([('source/lnk', 'target/lnk')], processed_links)
597
#class TestTerminalEncoding has been moved to test_osutils_encodings.py
598
# [bialix] 2006/12/26
601
class TestSetUnsetEnv(TestCase):
602
"""Test updating the environment"""
605
super(TestSetUnsetEnv, self).setUp()
607
self.assertEqual(None, os.environ.get('BZR_TEST_ENV_VAR'),
608
'Environment was not cleaned up properly.'
609
' Variable BZR_TEST_ENV_VAR should not exist.')
611
if 'BZR_TEST_ENV_VAR' in os.environ:
612
del os.environ['BZR_TEST_ENV_VAR']
614
self.addCleanup(cleanup)
617
"""Test that we can set an env variable"""
618
old = osutils.set_or_unset_env('BZR_TEST_ENV_VAR', 'foo')
619
self.assertEqual(None, old)
620
self.assertEqual('foo', os.environ.get('BZR_TEST_ENV_VAR'))
622
def test_double_set(self):
623
"""Test that we get the old value out"""
624
osutils.set_or_unset_env('BZR_TEST_ENV_VAR', 'foo')
625
old = osutils.set_or_unset_env('BZR_TEST_ENV_VAR', 'bar')
626
self.assertEqual('foo', old)
627
self.assertEqual('bar', os.environ.get('BZR_TEST_ENV_VAR'))
629
def test_unicode(self):
630
"""Environment can only contain plain strings
632
So Unicode strings must be encoded.
634
# Try a few different characters, to see if we can get
635
# one that will be valid in the user_encoding
636
possible_vals = [u'm\xb5', u'\xe1', u'\u0410']
637
for uni_val in possible_vals:
639
env_val = uni_val.encode(bzrlib.user_encoding)
640
except UnicodeEncodeError:
641
# Try a different character
646
raise TestSkipped('Cannot find a unicode character that works in'
647
' encoding %s' % (bzrlib.user_encoding,))
649
old = osutils.set_or_unset_env('BZR_TEST_ENV_VAR', uni_val)
650
self.assertEqual(env_val, os.environ.get('BZR_TEST_ENV_VAR'))
652
def test_unset(self):
653
"""Test that passing None will remove the env var"""
654
osutils.set_or_unset_env('BZR_TEST_ENV_VAR', 'foo')
655
old = osutils.set_or_unset_env('BZR_TEST_ENV_VAR', None)
656
self.assertEqual('foo', old)
657
self.assertEqual(None, os.environ.get('BZR_TEST_ENV_VAR'))
658
self.failIf('BZR_TEST_ENV_VAR' in os.environ)
661
class TestLocalTimeOffset(TestCase):
663
def test_local_time_offset(self):
664
"""Test that local_time_offset() returns a sane value."""
665
offset = osutils.local_time_offset()
666
self.assertTrue(isinstance(offset, int))
667
# Test that the offset is no more than a eighteen hours in
669
# Time zone handling is system specific, so it is difficult to
670
# do more specific tests, but a value outside of this range is
672
eighteen_hours = 18 * 3600
673
self.assertTrue(-eighteen_hours < offset < eighteen_hours)
675
def test_local_time_offset_with_timestamp(self):
676
"""Test that local_time_offset() works with a timestamp."""
677
offset = osutils.local_time_offset(1000000000.1234567)
678
self.assertTrue(isinstance(offset, int))
679
eighteen_hours = 18 * 3600
680
self.assertTrue(-eighteen_hours < offset < eighteen_hours)