1# Copyright (C) 2006-2010 Canonical Ltd 2# 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. 7# 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. 12# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 17 18"""Tree implementation tests for bzr. 19 20These test the conformance of all the tree variations to the expected API. 21Specific tests for individual variations are in other places such as: 22 - tests/per_workingtree/*.py. 23 - tests/test_tree.py 24 - tests/test_revision.py 25 - tests/test_workingtree.py 26""" 27 28import contextlib 29 30from breezy import ( 31 errors, 32 tests, 33 transform, 34 transport, 35 ) 36from breezy.git.tree import GitRevisionTree 37from breezy.git.workingtree import GitWorkingTreeFormat 38from breezy.tests.per_controldir.test_controldir import TestCaseWithControlDir 39from breezy.tests.per_workingtree import ( 40 make_scenarios as wt_make_scenarios, 41 make_scenario as wt_make_scenario, 42 ) 43from breezy.revisiontree import RevisionTree 44from breezy.tests import ( 45 features, 46 ) 47from breezy.workingtree import ( 48 format_registry, 49 ) 50from breezy.bzr.workingtree_4 import ( 51 DirStateRevisionTree, 52 WorkingTreeFormat4, 53 WorkingTreeFormat5, 54 ) 55 56 57def return_parameter(testcase, something): 58 """A trivial thunk to return its input.""" 59 return something 60 61 62def revision_tree_from_workingtree(testcase, tree): 63 """Create a revision tree from a working tree.""" 64 revid = tree.commit('save tree', allow_pointless=True, recursive=None) 65 return tree.branch.repository.revision_tree(revid) 66 67 68def _dirstate_tree_from_workingtree(testcase, tree): 69 revid = tree.commit('save tree', allow_pointless=True, recursive=None) 70 return tree.basis_tree() 71 72 73def preview_tree_pre(testcase, tree): 74 tt = tree.preview_transform() 75 testcase.addCleanup(tt.finalize) 76 preview_tree = tt.get_preview_tree() 77 preview_tree.set_parent_ids(tree.get_parent_ids()) 78 return preview_tree 79 80 81def preview_tree_post(testcase, tree): 82 basis = tree.basis_tree() 83 tt = basis.preview_transform() 84 testcase.addCleanup(tt.finalize) 85 tree.lock_read() 86 testcase.addCleanup(tree.unlock) 87 pp = None 88 es = contextlib.ExitStack() 89 testcase.addCleanup(es.close) 90 transform._prepare_revert_transform(es, basis, tree, tt, None, False, None, 91 basis, {}) 92 preview_tree = tt.get_preview_tree() 93 preview_tree.set_parent_ids(tree.get_parent_ids()) 94 return preview_tree 95 96 97class TestTreeImplementationSupport(tests.TestCaseWithTransport): 98 99 def test_revision_tree_from_workingtree_bzr(self): 100 tree = self.make_branch_and_tree('.', format='bzr') 101 tree = revision_tree_from_workingtree(self, tree) 102 self.assertIsInstance(tree, RevisionTree) 103 104 def test_revision_tree_from_workingtree(self): 105 tree = self.make_branch_and_tree('.', format='git') 106 tree = revision_tree_from_workingtree(self, tree) 107 self.assertIsInstance(tree, GitRevisionTree) 108 109 110class TestCaseWithTree(TestCaseWithControlDir): 111 112 def make_branch_and_tree(self, relpath): 113 bzrdir_format = self.workingtree_format.get_controldir_for_branch() 114 made_control = self.make_controldir(relpath, format=bzrdir_format) 115 made_control.create_repository() 116 b = made_control.create_branch() 117 if getattr(self, 'repo_is_remote', False): 118 # If the repo is remote, then we just create a local lightweight 119 # checkout 120 # XXX: This duplicates a lot of Branch.create_checkout, but we know 121 # we want a) lightweight, and b) a specific WT format. We also 122 # know that nothing should already exist, etc. 123 t = transport.get_transport(relpath) 124 t.ensure_base() 125 wt_dir = bzrdir_format.initialize_on_transport(t) 126 branch_ref = wt_dir.set_branch_reference(b) 127 wt = wt_dir.create_workingtree(None, from_branch=branch_ref) 128 else: 129 wt = self.workingtree_format.initialize(made_control) 130 return wt 131 132 def workingtree_to_test_tree(self, tree): 133 return self._workingtree_to_test_tree(self, tree) 134 135 def _convert_tree(self, tree, converter=None): 136 """helper to convert using the converter or a supplied one.""" 137 # convert that to the final shape 138 if converter is None: 139 converter = self.workingtree_to_test_tree 140 return converter(tree) 141 142 def get_tree_no_parents_no_content(self, empty_tree, converter=None): 143 """Make a tree with no parents and no contents from empty_tree. 144 145 :param empty_tree: A working tree with no content and no parents to 146 modify. 147 """ 148 if empty_tree.supports_setting_file_ids(): 149 empty_tree.set_root_id(b'empty-root-id') 150 return self._convert_tree(empty_tree, converter) 151 152 def _make_abc_tree(self, tree): 153 """setup an abc content tree.""" 154 files = ['a', 'b/', 'b/c'] 155 self.build_tree(files, line_endings='binary', 156 transport=tree.controldir.root_transport) 157 tree.add(files) 158 159 def get_tree_no_parents_abc_content(self, tree, converter=None): 160 """return a test tree with a, b/, b/c contents.""" 161 self._make_abc_tree(tree) 162 return self._convert_tree(tree, converter) 163 164 def get_tree_no_parents_abc_content_2(self, tree, converter=None): 165 """return a test tree with a, b/, b/c contents. 166 167 This variation changes the content of 'a' to foobar\n. 168 """ 169 self._make_abc_tree(tree) 170 with open(tree.basedir + '/a', 'wb') as f: 171 f.write(b'foobar\n') 172 return self._convert_tree(tree, converter) 173 174 def get_tree_no_parents_abc_content_3(self, tree, converter=None): 175 """return a test tree with a, b/, b/c contents. 176 177 This variation changes the executable flag of b/c to True. 178 """ 179 self._make_abc_tree(tree) 180 tt = tree.transform() 181 trans_id = tt.trans_id_tree_path('b/c') 182 tt.set_executability(True, trans_id) 183 tt.apply() 184 return self._convert_tree(tree, converter) 185 186 def get_tree_no_parents_abc_content_4(self, tree, converter=None): 187 """return a test tree with d, b/, b/c contents. 188 189 This variation renames a to d. 190 """ 191 self._make_abc_tree(tree) 192 tree.rename_one('a', 'd') 193 return self._convert_tree(tree, converter) 194 195 def get_tree_no_parents_abc_content_5(self, tree, converter=None): 196 """return a test tree with d, b/, b/c contents. 197 198 This variation renames a to d and alters its content to 'bar\n'. 199 """ 200 self._make_abc_tree(tree) 201 tree.rename_one('a', 'd') 202 with open(tree.basedir + '/d', 'wb') as f: 203 f.write(b'bar\n') 204 return self._convert_tree(tree, converter) 205 206 def get_tree_no_parents_abc_content_6(self, tree, converter=None): 207 """return a test tree with a, b/, e contents. 208 209 This variation renames b/c to e, and makes it executable. 210 """ 211 self._make_abc_tree(tree) 212 tt = tree.transform() 213 trans_id = tt.trans_id_tree_path('b/c') 214 parent_trans_id = tt.trans_id_tree_path('') 215 tt.adjust_path('e', parent_trans_id, trans_id) 216 tt.set_executability(True, trans_id) 217 tt.apply() 218 return self._convert_tree(tree, converter) 219 220 def get_tree_no_parents_abc_content_7(self, tree, converter=None): 221 """return a test tree with a, b/, d/e contents. 222 223 This variation adds a dir 'd' (b'd-id'), renames b to d/e. 224 """ 225 self._make_abc_tree(tree) 226 self.build_tree(['d/'], transport=tree.controldir.root_transport) 227 tree.add(['d']) 228 tt = tree.transform() 229 trans_id = tt.trans_id_tree_path('b') 230 parent_trans_id = tt.trans_id_tree_path('d') 231 tt.adjust_path('e', parent_trans_id, trans_id) 232 tt.apply() 233 return self._convert_tree(tree, converter) 234 235 def get_tree_with_subdirs_and_all_content_types(self): 236 """Return a test tree with subdirs and all content types. 237 See get_tree_with_subdirs_and_all_supported_content_types for details. 238 """ 239 return self.get_tree_with_subdirs_and_all_supported_content_types(True) 240 241 def get_tree_with_subdirs_and_all_supported_content_types(self, symlinks): 242 """Return a test tree with subdirs and all supported content types. 243 Some content types may not be created on some platforms 244 (like symlinks on native win32) 245 246 :param symlinks: control is symlink should be created in the tree. 247 Note: if you wish to automatically set this 248 parameters depending on underlying system, 249 please use value returned 250 by breezy.osutils.has_symlinks() function. 251 252 The returned tree has the following inventory: 253 ['', 254 '0file', 255 '1top-dir', 256 u'2utf\u1234file', 257 'symlink', # only if symlinks arg is True 258 '1top-dir/0file-in-1topdir', 259 '1top-dir/1dir-in-1topdir'] 260 where each component has the type of its name - 261 i.e. '1file..' is afile. 262 263 note that the order of the paths and fileids is deliberately 264 mismatched to ensure that the result order is path based. 265 """ 266 self.requireFeature(features.UnicodeFilenameFeature) 267 tree = self.make_branch_and_tree('.') 268 paths = ['0file', 269 '1top-dir/', 270 u'2utf\u1234file', 271 '1top-dir/0file-in-1topdir', 272 '1top-dir/1dir-in-1topdir/' 273 ] 274 self.build_tree(paths) 275 tree.add(paths) 276 tt = tree.transform() 277 if symlinks: 278 root_transaction_id = tt.trans_id_tree_path('') 279 tt.new_symlink('symlink', 280 root_transaction_id, 'link-target', b'symlink') 281 tt.apply() 282 return self.workingtree_to_test_tree(tree) 283 284 285def make_scenarios(transport_server, transport_readonly_server, formats): 286 """Generate test suites for each Tree implementation in breezy. 287 288 Currently this covers all working tree formats, and RevisionTree and 289 DirStateRevisionTree by committing a working tree to create the revision 290 tree. 291 """ 292 # TODO(jelmer): Test MemoryTree here 293 # TODO(jelmer): Test GitMemoryTree here 294 scenarios = wt_make_scenarios(transport_server, transport_readonly_server, 295 formats) 296 # now adjust the scenarios and add the non-working-tree tree scenarios. 297 for scenario in scenarios: 298 # for working tree format tests, preserve the tree 299 scenario[1]["_workingtree_to_test_tree"] = return_parameter 300 # add RevisionTree scenario 301 workingtree_format = format_registry.get_default() 302 scenarios.append((RevisionTree.__name__, 303 create_tree_scenario(transport_server, transport_readonly_server, 304 workingtree_format, revision_tree_from_workingtree,))) 305 scenarios.append((GitRevisionTree.__name__, 306 create_tree_scenario(transport_server, transport_readonly_server, 307 GitWorkingTreeFormat(), revision_tree_from_workingtree,))) 308 309 # also test WorkingTree4/5's RevisionTree implementation which is 310 # specialised. 311 # XXX: Ask igc if WT5 revision tree actually is different. 312 scenarios.append((DirStateRevisionTree.__name__ + ",WT4", 313 create_tree_scenario(transport_server, transport_readonly_server, 314 WorkingTreeFormat4(), _dirstate_tree_from_workingtree))) 315 scenarios.append((DirStateRevisionTree.__name__ + ",WT5", 316 create_tree_scenario(transport_server, transport_readonly_server, 317 WorkingTreeFormat5(), _dirstate_tree_from_workingtree))) 318 scenarios.append(("PreviewTree", create_tree_scenario(transport_server, 319 transport_readonly_server, workingtree_format, preview_tree_pre))) 320 scenarios.append(("PreviewTreePost", create_tree_scenario(transport_server, 321 transport_readonly_server, workingtree_format, preview_tree_post))) 322 return scenarios 323 324 325def create_tree_scenario(transport_server, transport_readonly_server, 326 workingtree_format, converter): 327 """Create a scenario for the specified converter 328 329 :param converter: A function that converts a workingtree into the 330 desired format. 331 :param workingtree_format: The particular workingtree format to 332 convert from. 333 :return: a (name, options) tuple, where options is a dict of values 334 to be used as members of the TestCase. 335 """ 336 scenario_options = wt_make_scenario(transport_server, 337 transport_readonly_server, 338 workingtree_format) 339 scenario_options["_workingtree_to_test_tree"] = converter 340 return scenario_options 341 342 343def load_tests(loader, standard_tests, pattern): 344 per_tree_mod_names = [ 345 'archive', 346 'annotate_iter', 347 'export', 348 'get_file_mtime', 349 'get_file_with_stat', 350 'get_root_id', 351 'get_symlink_target', 352 'ids', 353 'iter_search_rules', 354 'is_executable', 355 'list_files', 356 'locking', 357 'path_content_summary', 358 'revision_tree', 359 'symlinks', 360 'test_trees', 361 'transform', 362 'tree', 363 'walkdirs', 364 ] 365 submod_tests = loader.loadTestsFromModuleNames( 366 [__name__ + '.test_' + name 367 for name in per_tree_mod_names]) 368 scenarios = make_scenarios( 369 tests.default_transport, 370 # None here will cause a readonly decorator to be created 371 # by the TestCaseWithTransport.get_readonly_transport method. 372 None, 373 format_registry._get_all()) 374 # add the tests for the sub modules 375 return tests.multiply_tests(submod_tests, scenarios, standard_tests) 376