13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
19
from cStringIO import StringIO
29
revision as _mod_revision,
35
from bzrlib.symbol_versioning import deprecated_in
36
from bzrlib.tests import features, EncodingAdapter
37
from bzrlib.tests.blackbox.test_diff import subst_dates
38
from bzrlib.tests import (
22
from tempfile import TemporaryFile
24
from bzrlib import tests
25
from bzrlib.diff import (
35
from bzrlib.errors import BinaryFile, NoDiff, ExecutableMissing
36
import bzrlib.osutils as osutils
37
import bzrlib.transform as transform
38
import bzrlib.patiencediff
39
import bzrlib._patiencediff_py
40
from bzrlib.tests import (Feature, TestCase, TestCaseWithTransport,
41
TestCaseInTempDir, TestSkipped)
44
class _CompiledPatienceDiffFeature(Feature):
48
import bzrlib._patiencediff_c
53
def feature_name(self):
54
return 'bzrlib._patiencediff_c'
56
CompiledPatienceDiffFeature = _CompiledPatienceDiffFeature()
59
class _UnicodeFilename(Feature):
60
"""Does the filesystem support Unicode filenames?"""
65
except UnicodeEncodeError:
67
except (IOError, OSError):
68
# The filesystem allows the Unicode filename but the file doesn't
72
# The filesystem allows the Unicode filename and the file exists,
76
UnicodeFilename = _UnicodeFilename()
79
class TestUnicodeFilename(TestCase):
81
def test_probe_passes(self):
82
"""UnicodeFilename._probe passes."""
83
# We can't test much more than that because the behaviour depends
85
UnicodeFilename._probe()
43
88
def udiff_lines(old, new, allow_binary=False):
44
89
output = StringIO()
45
diff.internal_diff('old', old, 'new', new, output, allow_binary)
90
internal_diff('old', old, 'new', new, output, allow_binary)
47
92
return output.readlines()
126
169
self.check_patch(lines)
128
171
def test_external_diff_binary_lang_c(self):
129
173
for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
130
self.overrideEnv(lang, 'C')
131
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
132
# Older versions of diffutils say "Binary files", newer
133
# versions just say "Files".
134
self.assertContainsRe(lines[0], '(Binary f|F)iles old and new differ\n')
135
self.assertEquals(lines[1:], ['\n'])
174
old_env[lang] = osutils.set_or_unset_env(lang, 'C')
176
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
177
# Older versions of diffutils say "Binary files", newer
178
# versions just say "Files".
179
self.assertContainsRe(lines[0],
180
'(Binary f|F)iles old and new differ\n')
181
self.assertEquals(lines[1:], ['\n'])
183
for lang, old_val in old_env.iteritems():
184
osutils.set_or_unset_env(lang, old_val)
137
186
def test_no_external_diff(self):
138
187
"""Check that NoDiff is raised when diff is not available"""
139
# Make sure no 'diff' command is available
140
# XXX: Weird, using None instead of '' breaks the test -- vila 20101216
141
self.overrideEnv('PATH', '')
142
self.assertRaises(errors.NoDiff, diff.external_diff,
143
'old', ['boo\n'], 'new', ['goo\n'],
144
StringIO(), diff_opts=['-u'])
188
# Use os.environ['PATH'] to make sure no 'diff' command is available
189
orig_path = os.environ['PATH']
191
os.environ['PATH'] = ''
192
self.assertRaises(NoDiff, external_diff,
193
'old', ['boo\n'], 'new', ['goo\n'],
194
StringIO(), diff_opts=['-u'])
196
os.environ['PATH'] = orig_path
146
198
def test_internal_diff_default(self):
147
199
# Default internal diff encoding is utf8
148
200
output = StringIO()
149
diff.internal_diff(u'old_\xb5', ['old_text\n'],
150
u'new_\xe5', ['new_text\n'], output)
201
internal_diff(u'old_\xb5', ['old_text\n'],
202
u'new_\xe5', ['new_text\n'], output)
151
203
lines = output.getvalue().splitlines(True)
152
204
self.check_patch(lines)
153
205
self.assertEquals(['--- old_\xc2\xb5\n',
194
246
def test_internal_diff_no_content(self):
195
247
output = StringIO()
196
diff.internal_diff(u'old', [], u'new', [], output)
248
internal_diff(u'old', [], u'new', [], output)
197
249
self.assertEqual('', output.getvalue())
199
251
def test_internal_diff_no_changes(self):
200
252
output = StringIO()
201
diff.internal_diff(u'old', ['text\n', 'contents\n'],
202
u'new', ['text\n', 'contents\n'],
253
internal_diff(u'old', ['text\n', 'contents\n'],
254
u'new', ['text\n', 'contents\n'],
204
256
self.assertEqual('', output.getvalue())
206
258
def test_internal_diff_returns_bytes(self):
208
260
output = StringIO.StringIO()
209
diff.internal_diff(u'old_\xb5', ['old_text\n'],
210
u'new_\xe5', ['new_text\n'], output)
211
self.assertIsInstance(output.getvalue(), str,
261
internal_diff(u'old_\xb5', ['old_text\n'],
262
u'new_\xe5', ['new_text\n'], output)
263
self.failUnless(isinstance(output.getvalue(), str),
212
264
'internal_diff should return bytestrings')
214
def test_internal_diff_default_context(self):
216
diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
217
'same_text\n','same_text\n','old_text\n'],
218
'new', ['same_text\n','same_text\n','same_text\n',
219
'same_text\n','same_text\n','new_text\n'], output)
220
lines = output.getvalue().splitlines(True)
221
self.check_patch(lines)
222
self.assertEquals(['--- old\n',
234
def test_internal_diff_no_context(self):
236
diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
237
'same_text\n','same_text\n','old_text\n'],
238
'new', ['same_text\n','same_text\n','same_text\n',
239
'same_text\n','same_text\n','new_text\n'], output,
241
lines = output.getvalue().splitlines(True)
242
self.check_patch(lines)
243
self.assertEquals(['--- old\n',
252
def test_internal_diff_more_context(self):
254
diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
255
'same_text\n','same_text\n','old_text\n'],
256
'new', ['same_text\n','same_text\n','same_text\n',
257
'same_text\n','same_text\n','new_text\n'], output,
259
lines = output.getvalue().splitlines(True)
260
self.check_patch(lines)
261
self.assertEquals(['--- old\n',
278
class TestDiffFiles(tests.TestCaseInTempDir):
267
class TestDiffFiles(TestCaseInTempDir):
280
269
def test_external_diff_binary(self):
281
270
"""The output when using external diff should use diff's i18n error"""
1314
1232
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1316
_test_needs_features = [features.compiled_patiencediff_feature]
1234
_test_needs_features = [CompiledPatienceDiffFeature]
1318
1236
def setUp(self):
1319
1237
super(TestPatienceDiffLibFiles_c, self).setUp()
1320
from bzrlib import _patiencediff_c
1238
import bzrlib._patiencediff_c
1321
1239
self._PatienceSequenceMatcher = \
1322
_patiencediff_c.PatienceSequenceMatcher_c
1325
class TestUsingCompiledIfAvailable(tests.TestCase):
1240
bzrlib._patiencediff_c.PatienceSequenceMatcher_c
1243
class TestUsingCompiledIfAvailable(TestCase):
1327
1245
def test_PatienceSequenceMatcher(self):
1328
if features.compiled_patiencediff_feature.available():
1246
if CompiledPatienceDiffFeature.available():
1329
1247
from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
1330
1248
self.assertIs(PatienceSequenceMatcher_c,
1331
patiencediff.PatienceSequenceMatcher)
1249
bzrlib.patiencediff.PatienceSequenceMatcher)
1333
1251
from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
1334
1252
self.assertIs(PatienceSequenceMatcher_py,
1335
patiencediff.PatienceSequenceMatcher)
1253
bzrlib.patiencediff.PatienceSequenceMatcher)
1337
1255
def test_unique_lcs(self):
1338
if features.compiled_patiencediff_feature.available():
1256
if CompiledPatienceDiffFeature.available():
1339
1257
from bzrlib._patiencediff_c import unique_lcs_c
1340
1258
self.assertIs(unique_lcs_c,
1341
patiencediff.unique_lcs)
1259
bzrlib.patiencediff.unique_lcs)
1343
1261
from bzrlib._patiencediff_py import unique_lcs_py
1344
1262
self.assertIs(unique_lcs_py,
1345
patiencediff.unique_lcs)
1263
bzrlib.patiencediff.unique_lcs)
1347
1265
def test_recurse_matches(self):
1348
if features.compiled_patiencediff_feature.available():
1266
if CompiledPatienceDiffFeature.available():
1349
1267
from bzrlib._patiencediff_c import recurse_matches_c
1350
1268
self.assertIs(recurse_matches_c,
1351
patiencediff.recurse_matches)
1269
bzrlib.patiencediff.recurse_matches)
1353
1271
from bzrlib._patiencediff_py import recurse_matches_py
1354
1272
self.assertIs(recurse_matches_py,
1355
patiencediff.recurse_matches)
1358
class TestDiffFromTool(tests.TestCaseWithTransport):
1273
bzrlib.patiencediff.recurse_matches)
1276
class TestDiffFromTool(TestCaseWithTransport):
1360
1278
def test_from_string(self):
1361
diff_obj = diff.DiffFromTool.from_string('diff', None, None, None)
1279
diff_obj = DiffFromTool.from_string('diff', None, None, None)
1362
1280
self.addCleanup(diff_obj.finish)
1363
self.assertEqual(['diff', '@old_path', '@new_path'],
1281
self.assertEqual(['diff', '%(old_path)s', '%(new_path)s'],
1364
1282
diff_obj.command_template)
1366
1284
def test_from_string_u5(self):
1367
diff_obj = diff.DiffFromTool.from_string('diff "-u 5"',
1285
diff_obj = DiffFromTool.from_string('diff -u\\ 5', None, None, None)
1369
1286
self.addCleanup(diff_obj.finish)
1370
self.assertEqual(['diff', '-u 5', '@old_path', '@new_path'],
1287
self.assertEqual(['diff', '-u 5', '%(old_path)s', '%(new_path)s'],
1371
1288
diff_obj.command_template)
1372
1289
self.assertEqual(['diff', '-u 5', 'old-path', 'new-path'],
1373
1290
diff_obj._get_command('old-path', 'new-path'))
1375
def test_from_string_path_with_backslashes(self):
1376
self.requireFeature(features.backslashdir_feature)
1377
tool = 'C:\\Tools\\Diff.exe'
1378
diff_obj = diff.DiffFromTool.from_string(tool, None, None, None)
1379
self.addCleanup(diff_obj.finish)
1380
self.assertEqual(['C:\\Tools\\Diff.exe', '@old_path', '@new_path'],
1381
diff_obj.command_template)
1382
self.assertEqual(['C:\\Tools\\Diff.exe', 'old-path', 'new-path'],
1383
diff_obj._get_command('old-path', 'new-path'))
1385
1292
def test_execute(self):
1386
1293
output = StringIO()
1387
diff_obj = diff.DiffFromTool(['python', '-c',
1388
'print "@old_path @new_path"'],
1294
diff_obj = DiffFromTool(['python', '-c',
1295
'print "%(old_path)s %(new_path)s"'],
1390
1297
self.addCleanup(diff_obj.finish)
1391
1298
diff_obj._execute('old', 'new')
1392
1299
self.assertEqual(output.getvalue().rstrip(), 'old new')
1394
1301
def test_excute_missing(self):
1395
diff_obj = diff.DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
1302
diff_obj = DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
1397
1304
self.addCleanup(diff_obj.finish)
1398
e = self.assertRaises(errors.ExecutableMissing, diff_obj._execute,
1305
e = self.assertRaises(ExecutableMissing, diff_obj._execute, 'old',
1400
1307
self.assertEqual('a-tool-which-is-unlikely-to-exist could not be found'
1401
1308
' on this machine', str(e))
1403
def test_prepare_files_creates_paths_readable_by_windows_tool(self):
1404
self.requireFeature(features.AttribFeature)
1406
tree = self.make_branch_and_tree('tree')
1407
self.build_tree_contents([('tree/file', 'content')])
1408
tree.add('file', 'file-id')
1409
tree.commit('old tree')
1411
self.addCleanup(tree.unlock)
1412
basis_tree = tree.basis_tree()
1413
basis_tree.lock_read()
1414
self.addCleanup(basis_tree.unlock)
1415
diff_obj = diff.DiffFromTool(['python', '-c',
1416
'print "@old_path @new_path"'],
1417
basis_tree, tree, output)
1418
diff_obj._prepare_files('file-id', 'file', 'file')
1419
# The old content should be readonly
1420
self.assertReadableByAttrib(diff_obj._root, 'old\\file',
1422
# The new content should use the tree object, not a 'new' file anymore
1423
self.assertEndsWith(tree.basedir, 'work/tree')
1424
self.assertReadableByAttrib(tree.basedir, 'file', r'work\\tree\\file$')
1426
def assertReadableByAttrib(self, cwd, relpath, regex):
1427
proc = subprocess.Popen(['attrib', relpath],
1428
stdout=subprocess.PIPE,
1430
(result, err) = proc.communicate()
1431
self.assertContainsRe(result.replace('\r\n', '\n'), regex)
1433
1310
def test_prepare_files(self):
1434
1311
output = StringIO()
1435
1312
tree = self.make_branch_and_tree('tree')
1436
1313
self.build_tree_contents([('tree/oldname', 'oldcontent')])
1437
self.build_tree_contents([('tree/oldname2', 'oldcontent2')])
1438
1314
tree.add('oldname', 'file-id')
1439
tree.add('oldname2', 'file2-id')
1440
# Earliest allowable date on FAT32 filesystems is 1980-01-01
1441
tree.commit('old tree', timestamp=315532800)
1315
tree.commit('old tree', timestamp=0)
1442
1316
tree.rename_one('oldname', 'newname')
1443
tree.rename_one('oldname2', 'newname2')
1444
1317
self.build_tree_contents([('tree/newname', 'newcontent')])
1445
self.build_tree_contents([('tree/newname2', 'newcontent2')])
1446
1318
old_tree = tree.basis_tree()
1447
1319
old_tree.lock_read()
1448
1320
self.addCleanup(old_tree.unlock)
1449
1321
tree.lock_read()
1450
1322
self.addCleanup(tree.unlock)
1451
diff_obj = diff.DiffFromTool(['python', '-c',
1452
'print "@old_path @new_path"'],
1453
old_tree, tree, output)
1323
diff_obj = DiffFromTool(['python', '-c',
1324
'print "%(old_path)s %(new_path)s"'],
1325
old_tree, tree, output)
1454
1326
self.addCleanup(diff_obj.finish)
1455
1327
self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')
1456
1328
old_path, new_path = diff_obj._prepare_files('file-id', 'oldname',
1458
1330
self.assertContainsRe(old_path, 'old/oldname$')
1459
self.assertEqual(315532800, os.stat(old_path).st_mtime)
1460
self.assertContainsRe(new_path, 'tree/newname$')
1331
self.assertEqual(0, os.stat(old_path).st_mtime)
1332
self.assertContainsRe(new_path, 'new/newname$')
1461
1333
self.assertFileEqual('oldcontent', old_path)
1462
1334
self.assertFileEqual('newcontent', new_path)
1463
if osutils.host_os_dereferences_symlinks():
1335
if osutils.has_symlinks():
1464
1336
self.assertTrue(os.path.samefile('tree/newname', new_path))
1465
1337
# make sure we can create files with the same parent directories
1466
diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
1469
class TestDiffFromToolEncodedFilename(tests.TestCaseWithTransport):
1471
def test_encodable_filename(self):
1472
# Just checks file path for external diff tool.
1473
# We cannot change CPython's internal encoding used by os.exec*.
1475
diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
1477
for _, scenario in EncodingAdapter.encoding_scenarios:
1478
encoding = scenario['encoding']
1479
dirname = scenario['info']['directory']
1480
filename = scenario['info']['filename']
1482
self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1483
relpath = dirname + u'/' + filename
1484
fullpath = diffobj._safe_filename('safe', relpath)
1487
fullpath.encode(encoding).decode(encoding)
1489
self.assert_(fullpath.startswith(diffobj._root + '/safe'))
1491
def test_unencodable_filename(self):
1493
diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
1495
for _, scenario in EncodingAdapter.encoding_scenarios:
1496
encoding = scenario['encoding']
1497
dirname = scenario['info']['directory']
1498
filename = scenario['info']['filename']
1500
if encoding == 'iso-8859-1':
1501
encoding = 'iso-8859-2'
1503
encoding = 'iso-8859-1'
1505
self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1506
relpath = dirname + u'/' + filename
1507
fullpath = diffobj._safe_filename('safe', relpath)
1510
fullpath.encode(encoding).decode(encoding)
1512
self.assert_(fullpath.startswith(diffobj._root + '/safe'))
1515
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
1517
def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
1518
"""Call get_trees_and_branches_to_diff_locked."""
1519
return diff.get_trees_and_branches_to_diff_locked(
1520
path_list, revision_specs, old_url, new_url, self.addCleanup)
1522
def test_basic(self):
1523
tree = self.make_branch_and_tree('tree')
1524
(old_tree, new_tree,
1525
old_branch, new_branch,
1526
specific_files, extra_trees) = self.call_gtabtd(
1527
['tree'], None, None, None)
1529
self.assertIsInstance(old_tree, revisiontree.RevisionTree)
1530
self.assertEqual(_mod_revision.NULL_REVISION,
1531
old_tree.get_revision_id())
1532
self.assertEqual(tree.basedir, new_tree.basedir)
1533
self.assertEqual(tree.branch.base, old_branch.base)
1534
self.assertEqual(tree.branch.base, new_branch.base)
1535
self.assertIs(None, specific_files)
1536
self.assertIs(None, extra_trees)
1538
def test_with_rev_specs(self):
1539
tree = self.make_branch_and_tree('tree')
1540
self.build_tree_contents([('tree/file', 'oldcontent')])
1541
tree.add('file', 'file-id')
1542
tree.commit('old tree', timestamp=0, rev_id="old-id")
1543
self.build_tree_contents([('tree/file', 'newcontent')])
1544
tree.commit('new tree', timestamp=0, rev_id="new-id")
1546
revisions = [revisionspec.RevisionSpec.from_string('1'),
1547
revisionspec.RevisionSpec.from_string('2')]
1548
(old_tree, new_tree,
1549
old_branch, new_branch,
1550
specific_files, extra_trees) = self.call_gtabtd(
1551
['tree'], revisions, None, None)
1553
self.assertIsInstance(old_tree, revisiontree.RevisionTree)
1554
self.assertEqual("old-id", old_tree.get_revision_id())
1555
self.assertIsInstance(new_tree, revisiontree.RevisionTree)
1556
self.assertEqual("new-id", new_tree.get_revision_id())
1557
self.assertEqual(tree.branch.base, old_branch.base)
1558
self.assertEqual(tree.branch.base, new_branch.base)
1559
self.assertIs(None, specific_files)
1560
self.assertEqual(tree.basedir, extra_trees[0].basedir)
1338
diff_obj._prepare_files('file-id', 'oldname2', 'newname2')