1 require "my-assertions"
11 class SvnFsTest < Test::Unit::TestCase
23 assert_equal(Svn::Core.subr_version, Svn::Fs.version)
27 path = File.join(@tmp_path, "fs")
28 fs_type = Svn::Fs::TYPE_FSFS
29 config = {Svn::Fs::CONFIG_FS_TYPE => fs_type}
31 assert(!File.exist?(path))
33 callback = Proc.new do |fs|
34 assert(File.exist?(path))
35 assert_equal(fs_type, Svn::Fs.type(path))
36 fs.set_warning_func do |err|
40 assert_equal(path, fs.path)
42 yield(:create, [path, config], callback)
45 assert_raises(Svn::Error::FsAlreadyClose) do
49 yield(:delete, [path])
50 assert(!File.exist?(path))
54 assert_create do |method, args, callback|
55 Svn::Fs.__send__(method, *args, &callback)
59 def test_create_for_backward_compatibility
60 assert_create do |method, args, callback|
61 Svn::Fs::FileSystem.__send__(method, *args, &callback)
68 path = File.join(@wc_path, file)
71 ctx = make_context(log)
73 commit_info = ctx.commit(@wc_path)
74 rev = commit_info.revision
76 assert_equal(log, ctx.log_message(path, rev))
78 backup_path = File.join(@tmp_path, "back")
80 FileUtils.mv(@fs.path, backup_path)
81 FileUtils.mkdir_p(@fs.path)
83 assert_raises(Svn::Error::RaLocalReposOpenFailed) do
84 ctx.log_message(path, rev)
87 yield(backup_path, @fs.path)
88 assert_equal(log, ctx.log_message(path, rev))
92 assert_hotcopy do |src, dest|
93 Svn::Fs.hotcopy(src, dest)
97 def test_hotcopy_for_backward_compatibility
98 assert_hotcopy do |src, dest|
99 Svn::Fs::FileSystem.hotcopy(src, dest)
106 src = "sample source"
107 path_in_repos = "/#{file}"
108 path = File.join(@wc_path, file)
110 assert_nil(@fs.root.name)
111 assert_equal(Svn::Core::INVALID_REVNUM, @fs.root.base_revision)
113 ctx = make_context(log)
114 FileUtils.touch(path)
116 rev1 = ctx.commit(@wc_path).revision
117 file_id1 = @fs.root.node_id(path_in_repos)
119 assert_equal(rev1, @fs.root.revision)
120 assert_equal(Svn::Core::NODE_FILE, @fs.root.check_path(path_in_repos))
121 assert(@fs.root.file?(path_in_repos))
122 assert(!@fs.root.dir?(path_in_repos))
124 assert_equal([path_in_repos], @fs.root.paths_changed.keys)
125 info = @fs.root.paths_changed[path_in_repos]
126 assert(info.text_mod?)
129 File.open(path, "w") {|f| f.print(src)}
130 rev2 = ctx.commit(@wc_path).revision
131 file_id2 = @fs.root.node_id(path_in_repos)
133 assert_equal(src, @fs.root.file_contents(path_in_repos){|f| f.read})
134 assert_equal(src.length, @fs.root.file_length(path_in_repos))
135 assert_equal(MD5.new(src).hexdigest,
136 @fs.root.file_md5_checksum(path_in_repos))
138 assert_equal([path_in_repos], @fs.root.paths_changed.keys)
139 info = @fs.root.paths_changed[path_in_repos]
140 assert(info.text_mod?)
143 assert_equal([path_in_repos, rev2],
144 @fs.root.node_history(file).location)
145 assert_equal([path_in_repos, rev2],
146 @fs.root.node_history(file).prev.location)
147 assert_equal([path_in_repos, rev1],
148 @fs.root.node_history(file).prev.prev.location)
150 assert(!@fs.root.dir?(path_in_repos))
151 assert(@fs.root.file?(path_in_repos))
153 assert(file_id1.related?(file_id2))
154 assert_equal(1, file_id1.compare(file_id2))
155 assert_equal(1, file_id2.compare(file_id1))
157 assert_equal(rev2, @fs.root.node_created_rev(path_in_repos))
158 assert_equal(path_in_repos, @fs.root.node_created_path(path_in_repos))
160 assert_raises(Svn::Error::FsNotTxnRoot) do
161 @fs.root.set_node_prop(path_in_repos, "name", "value")
168 src = "sample source"
169 path_in_repos = "/#{file}"
170 path = File.join(@wc_path, file)
174 ctx = make_context(log)
175 File.open(path, "w") {|f| f.print(src)}
179 assert_raises(Svn::Error::FsNoSuchTransaction) do
180 @fs.open_txn("NOT-EXIST")
183 start_time = Time.now
184 txn1 = @fs.transaction
185 assert_equal([Svn::Core::PROP_REVISION_DATE], txn1.proplist.keys)
186 assert_instance_of(Time, txn1.proplist[Svn::Core::PROP_REVISION_DATE])
187 date = txn1.prop(Svn::Core::PROP_REVISION_DATE)
189 # Subversion's clock is more precise than Ruby's on
190 # Windows. So this test can fail intermittently because
191 # the begin and end of the range are the same (to 3
192 # decimal places), but the time from Subversion has 6
193 # decimal places so it looks like it's not in the range.
194 # So we just add a smidgen to the end of the Range.
195 assert_operator(start_time..(Time.now + 0.001), :include?, date)
196 txn1.set_prop(Svn::Core::PROP_REVISION_DATE, nil)
197 assert_equal([], txn1.proplist.keys)
198 assert_equal(youngest_rev, txn1.base_revision)
199 assert(txn1.root.txn_root?)
200 assert(!txn1.root.revision_root?)
201 assert_equal(txn1.name, txn1.root.name)
202 assert_equal(txn1.base_revision, txn1.root.base_revision)
204 @fs.transaction do |txn|
205 assert_nothing_raised do
206 @fs.open_txn(txn.name)
211 txn3 = @fs.transaction
213 assert_equal([txn1.name, txn3.name].sort, @fs.transactions.sort)
214 @fs.purge_txn(txn3.name)
215 assert_equal([txn1.name].sort, @fs.transactions.sort)
217 @fs.transaction do |txn|
218 assert(@fs.transactions.include?(txn.name))
220 assert(!@fs.transactions.include?(txn.name))
223 txn4 = @fs.transaction
224 assert_equal({}, txn1.root.node_proplist(path_in_repos))
225 assert_nil(txn1.root.node_prop(path_in_repos, prop_name))
226 txn1.root.set_node_prop(path_in_repos, prop_name, prop_value)
227 assert_equal(prop_value, txn1.root.node_prop(path_in_repos, prop_name))
228 assert_equal({prop_name => prop_value},
229 txn1.root.node_proplist(path_in_repos))
230 assert(txn1.root.props_changed?(path_in_repos, txn4.root, path_in_repos))
231 assert(!txn1.root.props_changed?(path_in_repos, txn1.root, path_in_repos))
232 txn1.root.set_node_prop(path_in_repos, prop_name, nil)
233 assert_nil(txn1.root.node_prop(path_in_repos, prop_name))
234 assert_equal({}, txn1.root.node_proplist(path_in_repos))
240 file2 = "sample2.txt"
241 file3 = "sample3.txt"
243 src = "sample source"
244 path_in_repos = "/#{file}"
245 path2_in_repos = "/#{file2}"
246 path3_in_repos = "/#{file3}"
247 dir_path_in_repos = "/#{dir}"
248 path = File.join(@wc_path, file)
249 path2 = File.join(@wc_path, file2)
250 path3 = File.join(@wc_path, file3)
251 dir_path = File.join(@wc_path, dir)
252 token = @fs.generate_lock_token
253 ctx = make_context(log)
255 @fs.transaction do |txn|
256 txn.root.make_file(file)
257 txn.root.make_dir(dir)
260 assert(File.exist?(path))
261 assert(File.directory?(dir_path))
263 @fs.transaction do |txn|
264 txn.root.copy(file2, @fs.root, file)
265 txn.root.delete(file)
269 assert(File.exist?(path))
270 assert(!File.exist?(path2))
272 @fs.transaction do |txn|
273 txn.root.copy(file2, @fs.root, file)
274 txn.root.delete(file)
277 assert(!File.exist?(path))
278 assert(File.exist?(path2))
280 prev_root = @fs.root(youngest_rev - 1)
281 assert(!prev_root.contents_changed?(file, @fs.root, file2))
282 File.open(path2, "w") {|f| f.print(src)}
284 assert(prev_root.contents_changed?(file, @fs.root, file2))
286 txn1 = @fs.transaction
287 access = Svn::Fs::Access.new(@author)
289 @fs.access.add_lock_token(token)
290 assert_equal([], @fs.get_locks(file2))
291 lock = @fs.lock(file2)
292 assert_equal(lock.token, @fs.get_lock(file2).token)
293 assert_equal([lock.token],
294 @fs.get_locks(file2).collect{|l| l.token})
295 @fs.unlock(file2, lock.token)
296 assert_equal([], @fs.get_locks(file2))
298 entries = @fs.root.dir_entries("/")
299 assert_equal([file2, dir].sort, entries.keys.sort)
300 assert_equal(@fs.root.node_id(path2_in_repos).to_s,
301 entries[file2].id.to_s)
302 assert_equal(@fs.root.node_id(dir_path_in_repos).to_s,
303 entries[dir].id.to_s)
305 @fs.transaction do |txn|
306 prev_root = @fs.root(youngest_rev - 2)
307 txn.root.revision_link(prev_root, file)
310 assert(File.exist?(path))
312 closest_root, closet_path = @fs.root.closest_copy(file2)
313 assert_equal(path2_in_repos, closet_path)
316 def test_delta(use_deprecated_api=false)
319 src = "a\nb\nc\nd\ne\n"
320 modified = "A\nb\nc\nd\nE\n"
321 result = "a\n\n\n\ne\n"
322 expected = "A\n\n\n\nE\n"
323 path_in_repos = "/#{file}"
324 path = File.join(@wc_path, file)
326 ctx = make_context(log)
328 File.open(path, "w") {|f| f.print(src)}
330 rev1 = ctx.ci(@wc_path).revision
332 File.open(path, "w") {|f| f.print(modified)}
333 @fs.transaction do |txn|
334 checksum = MD5.new(normalize_line_break(result)).hexdigest
335 stream = txn.root.apply_text(path_in_repos, checksum)
336 stream.write(normalize_line_break(result))
340 assert_equal(expected, File.open(path){|f| f.read})
342 rev2 = ctx.ci(@wc_path).revision
343 if use_deprecated_api
344 stream = @fs.root(rev2).file_delta_stream(@fs.root(rev1),
348 stream = @fs.root(rev1).file_delta_stream(path_in_repos,
354 stream.each{|w| data << w.new_data}
355 assert_equal(normalize_line_break(expected), data)
357 File.open(path, "w") {|f| f.print(src)}
358 rev3 = ctx.ci(@wc_path).revision
360 File.open(path, "w") {|f| f.print(modified)}
361 @fs.transaction do |txn|
362 base_checksum = MD5.new(normalize_line_break(src)).hexdigest
363 checksum = MD5.new(normalize_line_break(result)).hexdigest
364 handler = txn.root.apply_textdelta(path_in_repos,
365 base_checksum, checksum)
366 assert_raises(Svn::Error::ChecksumMismatch) do
372 def test_delta_with_deprecated_api
378 ctx = make_context(log)
379 ctx.checkout(@repos_uri, @wc_path)
380 ctx.mkdir(["#{@wc_path}/new_dir"])
382 start_time = Time.now
383 info = ctx.commit([@wc_path])
385 assert_equal(@author, info.author)
386 assert_equal(@fs.youngest_rev, info.revision)
387 assert_operator(start_time..(Time.now), :include?, info.date)
389 assert_equal(@author, @fs.prop(Svn::Core::PROP_REVISION_AUTHOR))
390 assert_equal(log, @fs.prop(Svn::Core::PROP_REVISION_LOG))
392 Svn::Core::PROP_REVISION_AUTHOR,
393 Svn::Core::PROP_REVISION_DATE,
394 Svn::Core::PROP_REVISION_LOG,
396 @fs.proplist.keys.sort)
397 @fs.set_prop(Svn::Core::PROP_REVISION_LOG, nil)
398 assert_nil(@fs.prop(Svn::Core::PROP_REVISION_LOG))
400 Svn::Core::PROP_REVISION_AUTHOR,
401 Svn::Core::PROP_REVISION_DATE,
403 @fs.proplist.keys.sort)
407 path = File.join(@tmp_path, "fs")
408 fs_type = Svn::Fs::TYPE_FSFS
409 config = {Svn::Fs::CONFIG_FS_TYPE => fs_type}
411 yield(:create, [path, config])
413 assert_nothing_raised do
414 yield(:recover, [path], Proc.new{})
418 def test_recover_for_backward_compatibility
419 assert_recover do |method, args, block|
420 Svn::Fs::FileSystem.__send__(method, *args, &block)
425 assert_recover do |method, args, block|
426 Svn::Fs.__send__(method, *args, &block)
430 def test_deleted_revision
433 path = File.join(@wc_path, file)
434 path_in_repos = "/#{file}"
435 ctx = make_context(log)
437 FileUtils.touch(path)
439 rev1 = ctx.ci(@wc_path).revision
442 rev2 = ctx.ci(@wc_path).revision
444 FileUtils.touch(path)
446 rev3 = ctx.ci(@wc_path).revision
449 rev4 = ctx.ci(@wc_path).revision
451 assert_equal(Svn::Core::INVALID_REVNUM,
452 @fs.deleted_revision(path_in_repos, 0, rev4))
453 assert_equal(rev2, @fs.deleted_revision(path_in_repos, rev1, rev4))
454 assert_equal(Svn::Core::INVALID_REVNUM,
455 @fs.deleted_revision(path_in_repos, rev2, rev4))
456 assert_equal(rev4, @fs.deleted_revision(path_in_repos, rev3, rev4))
457 assert_equal(Svn::Core::INVALID_REVNUM,
458 @fs.deleted_revision(path_in_repos, rev4, rev4))
465 trunk = File.join(@wc_path, "trunk")
466 branch = File.join(@wc_path, "branch")
467 trunk_path = File.join(trunk, file)
468 branch_path = File.join(branch, file)
469 trunk_in_repos = "/trunk"
470 branch_in_repos = "/branch"
472 ctx = make_context(log)
473 ctx.mkdir(trunk, branch)
474 File.open(trunk_path, "w") {}
475 File.open(branch_path, "w") {}
478 rev1 = ctx.commit(@wc_path).revision
480 File.open(branch_path, "w") {|f| f.print(src)}
481 rev2 = ctx.commit(@wc_path).revision
483 assert_equal({}, @fs.root.mergeinfo(trunk_in_repos))
484 ctx.merge(branch, rev1, branch, rev2, trunk)
485 assert_equal({}, @fs.root.mergeinfo(trunk_in_repos))
487 rev3 = ctx.commit(@wc_path).revision
488 mergeinfo = Svn::Core::MergeInfo.parse("#{branch_in_repos}:2")
489 assert_equal({trunk_in_repos => mergeinfo},
490 @fs.root.mergeinfo(trunk_in_repos))
493 rev4 = ctx.commit(@wc_path).revision
495 ctx.merge(branch, rev3, branch, rev4, trunk)
496 assert(!File.exist?(trunk_path))
497 rev5 = ctx.commit(@wc_path).revision
498 assert_equal({trunk_in_repos => Svn::Core::MergeInfo.parse("#{branch_in_repos}:2,4")},
499 @fs.root.mergeinfo(trunk_in_repos))